From 8199764282631956bf98a9a705fc43e4dc85fea7 Mon Sep 17 00:00:00 2001 From: Khemi Date: Tue, 28 Dec 2021 16:53:46 +0000 Subject: [PATCH 01/12] complete lab task and extension A with rug instead of BigUint --- register-machines/Cargo.toml | 3 +- register-machines/src/main.rs | 159 ++++++++++++++++++++++++++++------ 2 files changed, 135 insertions(+), 27 deletions(-) diff --git a/register-machines/Cargo.toml b/register-machines/Cargo.toml index b981279..b0f7446 100644 --- a/register-machines/Cargo.toml +++ b/register-machines/Cargo.toml @@ -6,5 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -num-bigint = "0.4.3" -num-traits = "0.2.14" \ No newline at end of file +rug = "1.14.0" \ No newline at end of file diff --git a/register-machines/src/main.rs b/register-machines/src/main.rs index ee82b77..c1f9720 100644 --- a/register-machines/src/main.rs +++ b/register-machines/src/main.rs @@ -1,5 +1,7 @@ -use std::collections::HashMap; +use std::{collections::HashMap, vec}; +use rug::{Integer, ops::Pow}; +type Godel = Integer; type Label = usize; type Register = u128; type State = (Label, HashMap); @@ -11,37 +13,107 @@ enum Instruction { Halt } +impl Instruction { + pub fn encode_instruction(&self) -> Godel { + match self { + Add(i, j) => + encode_pair1(Godel::from(2 * i), Godel::from(*j)), + Sub(i, j, k) => + encode_pair1(Godel::from(2 * i + 1), + encode_pair2(Godel::from(*j), Godel::from(*k)) + ), + Halt => Godel::ZERO + } + } +} + use Instruction::*; fn eval_program(program: &[Instruction], state: &State) -> State { - unimplemented!(); + let (mut l, mut rs) = state.clone(); + let mut ins = program[l]; + while ins != Halt { + match ins { + Add(i, j) => { + let v = rs.entry(i).or_insert(0); + *v += 1; + l = j; + }, + Sub(i, j, k) => { + let v = rs.entry(i).or_insert(0); + if *v != 0 { + *v -= 1; + l = j; + } else { + l = k; + } + }, + _ => panic!("unreachable") + } + ins = if l >= program.len() { Halt } else { program[l] }; + } + (l, rs) } // <> = (2^x)*(2y+1) -fn encode_pair1(x: u128, y: u128) -> u128 { - unimplemented!(); +fn encode_pair1(x: Godel, y: Godel) -> Godel { + let left: Godel = Godel::from(2).pow(x.to_u32_wrapping()); + let right: Godel = Godel::from(2) * y + Godel::from(1); + left * right } // = (2^x)*(2y+1)-1 -fn encode_pair2(x: u128, y: u128) -> u128 { - unimplemented!(); +fn encode_pair2(x: Godel, y: Godel) -> Godel { + encode_pair1(x, y) - Godel::from(1) +} +fn encode_list_to_godel(l: &[Godel]) -> Godel { + if l.is_empty() { return Godel::ZERO; } + encode_pair1(l[0].clone(), encode_list_to_godel(&l[1..])) } -fn encode_list_to_godel(l: &[u128]) -> u128 { - unimplemented!(); +fn encode_program_to_list(program: &[Instruction]) -> Vec { + program.iter().map(Instruction::encode_instruction).collect() } -fn encode_program_to_list(program: &[Instruction]) -> Vec { - unimplemented!(); +fn trailing_zeros_in_binary(x: Godel) -> Godel { + let mut b = x; + let mut c = Godel::new(); + if b != 0 { + b = (b.clone() ^ (b - 1)) >> 1; + while b != 0 { + c += 1; + b >>= 1; + } + } + c + // in theory it should be infinity, but rug does not support such calls +} +fn decode_instruction(ins: Godel) -> Instruction { + if ins == 0 { return Halt; } + let (x, y) = decode_pair1(ins); + let i: Godel = x.clone() / 2; + if x % 2 != 0 { + let (j, k) = decode_pair2(y); + Sub(i.try_into().unwrap(), j.try_into().unwrap(), k.try_into().unwrap()) + } else { + Add(i.try_into().unwrap(), y.try_into().unwrap()) + } } // a = (2^x)*(2y+1) -fn decode_pair1(a: u128) -> (u128, u128) { - unimplemented!(); +fn decode_pair1(a: Godel) -> (Godel, Godel) { + let x: Godel = trailing_zeros_in_binary(a.clone()); + let z: Godel = a / Godel::from(2).pow(x.to_u32_wrapping()); + let y: Godel = (z - 1) / 2; + (x, y) } // a = (2^x)*(2y+1)-1 -fn decode_pair2(a: u128) -> (u128, u128) { - unimplemented!(); +fn decode_pair2(a: Godel) -> (Godel, Godel) { + decode_pair1(a + 1) } -fn decode_godel_to_list(g: u128) -> Vec { - unimplemented!(); +fn decode_godel_to_list(g: Godel) -> Vec { + if g == 0 { return Vec::new(); } + let (x, xs) = decode_pair1(g); + let mut gs = vec![x]; + gs.splice(gs.len().., decode_godel_to_list(xs)); + gs } -fn decode_list_to_program(program: &[u128]) -> Vec { - unimplemented!(); +fn decode_list_to_program(program: &[Godel]) -> Vec { + program.iter().map(|x| decode_instruction(x.clone())).collect() } fn main() { @@ -49,30 +121,67 @@ fn main() { mod test { use crate::*; + #[test] + fn halt_encodes_to_godel_zero_num() { + let g = Halt.encode_instruction(); + assert_eq!(g, Godel::ZERO); + } + + #[test] + fn godel_zero_num_decodes_to_halt() { + let ins = decode_instruction(Godel::ZERO); + assert_eq!(ins, Halt); + } + #[test] fn godel_num_to_godel_list() { - let n = 2u128.pow(46) * 20483; - let godel_list = decode_godel_to_list(n); - let true_godel_list = vec![46, 0, 10, 1]; + let n = Godel::from(2).pow(46) * 20483; + let godel_list: Vec = decode_godel_to_list(n); + let true_godel_list: Vec = vec![46, 0, 10, 1].iter().map(|x| Godel::from(x.clone())).collect(); assert_eq!(godel_list, true_godel_list) } + #[test] + fn godel_num_to_godel_list_large_num() { + let n = Godel::from(2).pow(216) * 833; + let godel_list: Vec = decode_godel_to_list(n); + let true_godel_list: Vec = vec![216, 5, 1, 0].iter().map(|x| Godel::from(x.clone())).collect(); + assert_eq!(godel_list, true_godel_list) + } + #[test] fn godel_list_to_godel_num() { - let godel_num = encode_list_to_godel(&[46, 0, 10, 1]); + let true_godel_list: Vec = [46, 0, 10, 1].iter().map(|x| Godel::from(x.clone())).collect(); + let godel_num: Godel = encode_list_to_godel(&true_godel_list); assert_eq!(godel_num, 2u128.pow(46) * 20483) } #[test] fn godel_list_to_program() { - let program = decode_list_to_program(&vec![46, 0, 10, 1]); + let true_godel_list: Vec = [46, 0, 10, 1].iter().map(|x| Godel::from(x.clone())).collect(); + let program = decode_list_to_program(&true_godel_list); assert_eq!(program, vec![Sub(0, 2, 1), Halt, Sub(0, 0, 1), Add(0, 0)]) } + #[test] + fn godel_list_to_program_2() { + let true_godel_list: Vec = [408, 2272, 7, 192, 8064, 144, 0].iter().map(|x| Godel::from(x.clone())).collect(); + let program = decode_list_to_program(&true_godel_list); + assert_eq!(program, vec![Sub(1, 1, 6), Sub(2, 2, 4), Add(0, 3), Add(3, 1), Sub(3, 5, 0), Add(2, 4), Halt]) + } + #[test] fn program_to_godel_list() { - let program = encode_program_to_list(&[Sub(0, 2, 1), Halt, Sub(0, 0, 1), Add(0, 0)]); - assert_eq!(program, [46, 0, 10, 1]) + let program: Vec = encode_program_to_list(&[Sub(0, 2, 1), Halt, Sub(0, 0, 1), Add(0, 0)]); + let true_godel_list: Vec = [46, 0, 10, 1].iter().map(|x| Godel::from(x.clone())).collect(); + assert_eq!(program, true_godel_list) + } + + #[test] + fn program_to_godel_list_2() { + let program: Vec = encode_program_to_list(&[Sub(1, 1, 6), Sub(2, 2, 4), Add(0, 3), Add(3, 1), Sub(3, 5, 0), Add(2, 4), Halt]); + let true_godel_list: Vec = [408, 2272, 7, 192, 8064, 144, 0].iter().map(|x| Godel::from(x.clone())).collect(); + assert_eq!(program, true_godel_list) } #[test] From 871621f65d92ccf8afbf667af024bf271f38fa70 Mon Sep 17 00:00:00 2001 From: Khemi Date: Tue, 28 Dec 2021 17:37:11 +0000 Subject: [PATCH 02/12] update state to store unbounded int, and pass references into functions --- register-machines/src/main.rs | 58 +++++++++++++++++------------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/register-machines/src/main.rs b/register-machines/src/main.rs index c1f9720..985ac1e 100644 --- a/register-machines/src/main.rs +++ b/register-machines/src/main.rs @@ -1,10 +1,10 @@ use std::{collections::HashMap, vec}; -use rug::{Integer, ops::Pow}; +use rug::{Integer, ops::Pow, Complete}; type Godel = Integer; type Label = usize; type Register = u128; -type State = (Label, HashMap); +type State = (Label, HashMap); #[derive(Clone, Copy, Debug, PartialEq)] enum Instruction { @@ -17,10 +17,10 @@ impl Instruction { pub fn encode_instruction(&self) -> Godel { match self { Add(i, j) => - encode_pair1(Godel::from(2 * i), Godel::from(*j)), + encode_pair1(&Godel::from(2 * i), &Godel::from(*j)), Sub(i, j, k) => - encode_pair1(Godel::from(2 * i + 1), - encode_pair2(Godel::from(*j), Godel::from(*k)) + encode_pair1(&Godel::from(2 * i + 1), + &encode_pair2(&Godel::from(*j), &Godel::from(*k)) ), Halt => Godel::ZERO } @@ -34,12 +34,12 @@ fn eval_program(program: &[Instruction], state: &State) -> State { while ins != Halt { match ins { Add(i, j) => { - let v = rs.entry(i).or_insert(0); + let v = rs.entry(i).or_insert(Godel::new()); *v += 1; l = j; }, Sub(i, j, k) => { - let v = rs.entry(i).or_insert(0); + let v = rs.entry(i).or_insert(Godel::new()); if *v != 0 { *v -= 1; l = j; @@ -54,24 +54,24 @@ fn eval_program(program: &[Instruction], state: &State) -> State { (l, rs) } // <> = (2^x)*(2y+1) -fn encode_pair1(x: Godel, y: Godel) -> Godel { +fn encode_pair1(x: &Godel, y: &Godel) -> Godel { let left: Godel = Godel::from(2).pow(x.to_u32_wrapping()); let right: Godel = Godel::from(2) * y + Godel::from(1); left * right } // = (2^x)*(2y+1)-1 -fn encode_pair2(x: Godel, y: Godel) -> Godel { +fn encode_pair2(x: &Godel, y: &Godel) -> Godel { encode_pair1(x, y) - Godel::from(1) } fn encode_list_to_godel(l: &[Godel]) -> Godel { if l.is_empty() { return Godel::ZERO; } - encode_pair1(l[0].clone(), encode_list_to_godel(&l[1..])) + encode_pair1(&l[0], &encode_list_to_godel(&l[1..])) } fn encode_program_to_list(program: &[Instruction]) -> Vec { program.iter().map(Instruction::encode_instruction).collect() } -fn trailing_zeros_in_binary(x: Godel) -> Godel { - let mut b = x; +fn trailing_zeros_in_binary(x: &Godel) -> Godel { + let mut b = x.clone(); let mut c = Godel::new(); if b != 0 { b = (b.clone() ^ (b - 1)) >> 1; @@ -83,37 +83,37 @@ fn trailing_zeros_in_binary(x: Godel) -> Godel { c // in theory it should be infinity, but rug does not support such calls } -fn decode_instruction(ins: Godel) -> Instruction { - if ins == 0 { return Halt; } +fn decode_instruction(ins: &Godel) -> Instruction { + if *ins == 0 { return Halt; } let (x, y) = decode_pair1(ins); let i: Godel = x.clone() / 2; if x % 2 != 0 { - let (j, k) = decode_pair2(y); + let (j, k) = decode_pair2(&y); Sub(i.try_into().unwrap(), j.try_into().unwrap(), k.try_into().unwrap()) } else { Add(i.try_into().unwrap(), y.try_into().unwrap()) } } // a = (2^x)*(2y+1) -fn decode_pair1(a: Godel) -> (Godel, Godel) { - let x: Godel = trailing_zeros_in_binary(a.clone()); - let z: Godel = a / Godel::from(2).pow(x.to_u32_wrapping()); +fn decode_pair1(a: &Godel) -> (Godel, Godel) { + let x: Godel = trailing_zeros_in_binary(a); + let z = Godel::from(a >> x.to_u32_wrapping()); let y: Godel = (z - 1) / 2; (x, y) } // a = (2^x)*(2y+1)-1 -fn decode_pair2(a: Godel) -> (Godel, Godel) { - decode_pair1(a + 1) +fn decode_pair2(a: &Godel) -> (Godel, Godel) { + decode_pair1(&Godel::from(a + 1)) } -fn decode_godel_to_list(g: Godel) -> Vec { - if g == 0 { return Vec::new(); } +fn decode_godel_to_list(g: &Godel) -> Vec { + if *g == 0 { return Vec::new(); } let (x, xs) = decode_pair1(g); let mut gs = vec![x]; - gs.splice(gs.len().., decode_godel_to_list(xs)); + gs.splice(gs.len().., decode_godel_to_list(&xs)); gs } fn decode_list_to_program(program: &[Godel]) -> Vec { - program.iter().map(|x| decode_instruction(x.clone())).collect() + program.iter().map(|x| decode_instruction(x)).collect() } fn main() { @@ -129,14 +129,14 @@ mod test { #[test] fn godel_zero_num_decodes_to_halt() { - let ins = decode_instruction(Godel::ZERO); + let ins = decode_instruction(&Godel::ZERO); assert_eq!(ins, Halt); } #[test] fn godel_num_to_godel_list() { let n = Godel::from(2).pow(46) * 20483; - let godel_list: Vec = decode_godel_to_list(n); + let godel_list: Vec = decode_godel_to_list(&n); let true_godel_list: Vec = vec![46, 0, 10, 1].iter().map(|x| Godel::from(x.clone())).collect(); assert_eq!(godel_list, true_godel_list) } @@ -144,7 +144,7 @@ mod test { #[test] fn godel_num_to_godel_list_large_num() { let n = Godel::from(2).pow(216) * 833; - let godel_list: Vec = decode_godel_to_list(n); + let godel_list: Vec = decode_godel_to_list(&n); let true_godel_list: Vec = vec![216, 5, 1, 0].iter().map(|x| Godel::from(x.clone())).collect(); assert_eq!(godel_list, true_godel_list) } @@ -199,14 +199,14 @@ mod test { &program, &( 0, - HashMap::<_, _>::from_iter(IntoIter::new([(0, 0), (1, 7)])) + HashMap::<_, _>::from_iter(IntoIter::new([(0, Godel::from(0)), (1, Godel::from(7))])) ), ); assert_eq!( final_state, ( 4, - HashMap::<_, _>::from_iter(IntoIter::new([(0, 2), (1, 0)])) + HashMap::<_, _>::from_iter(IntoIter::new([(0, Godel::from(2)), (1, Godel::from(0))])) ) ) } From d0d141f2f6406a3b998edcff8b4074c2da15324e Mon Sep 17 00:00:00 2001 From: Khemi Date: Wed, 29 Dec 2021 13:55:25 +0000 Subject: [PATCH 03/12] cleanup and optimisation --- register-machines/src/main.rs | 68 ++++++++++++++++------------------- 1 file changed, 30 insertions(+), 38 deletions(-) diff --git a/register-machines/src/main.rs b/register-machines/src/main.rs index 985ac1e..9300dd9 100644 --- a/register-machines/src/main.rs +++ b/register-machines/src/main.rs @@ -1,5 +1,5 @@ use std::{collections::HashMap, vec}; -use rug::{Integer, ops::Pow, Complete}; +use rug::Integer; type Godel = Integer; type Label = usize; @@ -13,29 +13,13 @@ enum Instruction { Halt } -impl Instruction { - pub fn encode_instruction(&self) -> Godel { - match self { - Add(i, j) => - encode_pair1(&Godel::from(2 * i), &Godel::from(*j)), - Sub(i, j, k) => - encode_pair1(&Godel::from(2 * i + 1), - &encode_pair2(&Godel::from(*j), &Godel::from(*k)) - ), - Halt => Godel::ZERO - } - } -} - use Instruction::*; fn eval_program(program: &[Instruction], state: &State) -> State { let (mut l, mut rs) = state.clone(); - let mut ins = program[l]; - while ins != Halt { - match ins { + while l < program.len() { + match program[l] { Add(i, j) => { - let v = rs.entry(i).or_insert(Godel::new()); - *v += 1; + *rs.entry(i).or_insert(Godel::new()) += 1; l = j; }, Sub(i, j, k) => { @@ -47,29 +31,38 @@ fn eval_program(program: &[Instruction], state: &State) -> State { l = k; } }, - _ => panic!("unreachable") + Halt => { break; } } - ins = if l >= program.len() { Halt } else { program[l] }; } (l, rs) } +fn encode_instruction(ins: &Instruction) -> Godel { + match ins { + Add(i, j) => + encode_pair1(&Godel::from(2 * i), &Godel::from(*j)), + Sub(i, j, k) => + encode_pair1(&Godel::from(2 * i + 1), + &encode_pair2(&Godel::from(*j), &Godel::from(*k)) + ), + Halt => Godel::ZERO + } +} // <> = (2^x)*(2y+1) fn encode_pair1(x: &Godel, y: &Godel) -> Godel { - let left: Godel = Godel::from(2).pow(x.to_u32_wrapping()); - let right: Godel = Godel::from(2) * y + Godel::from(1); - left * right + (Godel::from(2) * y + 1) << x.to_u32_wrapping() } // = (2^x)*(2y+1)-1 fn encode_pair2(x: &Godel, y: &Godel) -> Godel { - encode_pair1(x, y) - Godel::from(1) + encode_pair1(x, y) - 1 } fn encode_list_to_godel(l: &[Godel]) -> Godel { if l.is_empty() { return Godel::ZERO; } encode_pair1(&l[0], &encode_list_to_godel(&l[1..])) } fn encode_program_to_list(program: &[Instruction]) -> Vec { - program.iter().map(Instruction::encode_instruction).collect() + program.iter().map(|x| encode_instruction(x)).collect() } +// Returns 0 if there are no trailing zeros, else return trailing zero count (in binary) fn trailing_zeros_in_binary(x: &Godel) -> Godel { let mut b = x.clone(); let mut c = Godel::new(); @@ -81,7 +74,6 @@ fn trailing_zeros_in_binary(x: &Godel) -> Godel { } } c - // in theory it should be infinity, but rug does not support such calls } fn decode_instruction(ins: &Godel) -> Instruction { if *ins == 0 { return Halt; } @@ -123,7 +115,7 @@ mod test { use crate::*; #[test] fn halt_encodes_to_godel_zero_num() { - let g = Halt.encode_instruction(); + let g = encode_instruction(&Halt); assert_eq!(g, Godel::ZERO); } @@ -135,37 +127,37 @@ mod test { #[test] fn godel_num_to_godel_list() { - let n = Godel::from(2).pow(46) * 20483; + let n = Godel::from(20483) << 46; let godel_list: Vec = decode_godel_to_list(&n); - let true_godel_list: Vec = vec![46, 0, 10, 1].iter().map(|x| Godel::from(x.clone())).collect(); + let true_godel_list: Vec = vec![46, 0, 10, 1].iter().map(|x| Godel::from(*x)).collect(); assert_eq!(godel_list, true_godel_list) } #[test] fn godel_num_to_godel_list_large_num() { - let n = Godel::from(2).pow(216) * 833; + let n = Godel::from(833) << 216; let godel_list: Vec = decode_godel_to_list(&n); - let true_godel_list: Vec = vec![216, 5, 1, 0].iter().map(|x| Godel::from(x.clone())).collect(); + let true_godel_list: Vec = vec![216, 5, 1, 0].iter().map(|x| Godel::from(*x)).collect(); assert_eq!(godel_list, true_godel_list) } #[test] fn godel_list_to_godel_num() { - let true_godel_list: Vec = [46, 0, 10, 1].iter().map(|x| Godel::from(x.clone())).collect(); + let true_godel_list: Vec = [46, 0, 10, 1].iter().map(|x| Godel::from(*x)).collect(); let godel_num: Godel = encode_list_to_godel(&true_godel_list); assert_eq!(godel_num, 2u128.pow(46) * 20483) } #[test] fn godel_list_to_program() { - let true_godel_list: Vec = [46, 0, 10, 1].iter().map(|x| Godel::from(x.clone())).collect(); + let true_godel_list: Vec = [46, 0, 10, 1].iter().map(|x| Godel::from(*x)).collect(); let program = decode_list_to_program(&true_godel_list); assert_eq!(program, vec![Sub(0, 2, 1), Halt, Sub(0, 0, 1), Add(0, 0)]) } #[test] fn godel_list_to_program_2() { - let true_godel_list: Vec = [408, 2272, 7, 192, 8064, 144, 0].iter().map(|x| Godel::from(x.clone())).collect(); + let true_godel_list: Vec = [408, 2272, 7, 192, 8064, 144, 0].iter().map(|x| Godel::from(*x)).collect(); let program = decode_list_to_program(&true_godel_list); assert_eq!(program, vec![Sub(1, 1, 6), Sub(2, 2, 4), Add(0, 3), Add(3, 1), Sub(3, 5, 0), Add(2, 4), Halt]) } @@ -173,14 +165,14 @@ mod test { #[test] fn program_to_godel_list() { let program: Vec = encode_program_to_list(&[Sub(0, 2, 1), Halt, Sub(0, 0, 1), Add(0, 0)]); - let true_godel_list: Vec = [46, 0, 10, 1].iter().map(|x| Godel::from(x.clone())).collect(); + let true_godel_list: Vec = [46, 0, 10, 1].iter().map(|x| Godel::from(*x)).collect(); assert_eq!(program, true_godel_list) } #[test] fn program_to_godel_list_2() { let program: Vec = encode_program_to_list(&[Sub(1, 1, 6), Sub(2, 2, 4), Add(0, 3), Add(3, 1), Sub(3, 5, 0), Add(2, 4), Halt]); - let true_godel_list: Vec = [408, 2272, 7, 192, 8064, 144, 0].iter().map(|x| Godel::from(x.clone())).collect(); + let true_godel_list: Vec = [408, 2272, 7, 192, 8064, 144, 0].iter().map(|x| Godel::from(*x)).collect(); assert_eq!(program, true_godel_list) } From 6a59e233f2d3826375f5b8305b5b891bef13994d Mon Sep 17 00:00:00 2001 From: Khemi Date: Tue, 28 Dec 2021 16:53:46 +0000 Subject: [PATCH 04/12] complete lab task and extension A with rug instead of BigUint --- register-machines/Cargo.toml | 3 +- register-machines/src/main.rs | 159 ++++++++++++++++++++++++++++------ 2 files changed, 135 insertions(+), 27 deletions(-) diff --git a/register-machines/Cargo.toml b/register-machines/Cargo.toml index b537379..9c26d17 100644 --- a/register-machines/Cargo.toml +++ b/register-machines/Cargo.toml @@ -10,5 +10,4 @@ name = "reference" path = "src/reference_solution.rs" [dependencies] -num-bigint = "0.4.3" -num-traits = "0.2.14" \ No newline at end of file +rug = "1.14.0" \ No newline at end of file diff --git a/register-machines/src/main.rs b/register-machines/src/main.rs index ee82b77..c1f9720 100644 --- a/register-machines/src/main.rs +++ b/register-machines/src/main.rs @@ -1,5 +1,7 @@ -use std::collections::HashMap; +use std::{collections::HashMap, vec}; +use rug::{Integer, ops::Pow}; +type Godel = Integer; type Label = usize; type Register = u128; type State = (Label, HashMap); @@ -11,37 +13,107 @@ enum Instruction { Halt } +impl Instruction { + pub fn encode_instruction(&self) -> Godel { + match self { + Add(i, j) => + encode_pair1(Godel::from(2 * i), Godel::from(*j)), + Sub(i, j, k) => + encode_pair1(Godel::from(2 * i + 1), + encode_pair2(Godel::from(*j), Godel::from(*k)) + ), + Halt => Godel::ZERO + } + } +} + use Instruction::*; fn eval_program(program: &[Instruction], state: &State) -> State { - unimplemented!(); + let (mut l, mut rs) = state.clone(); + let mut ins = program[l]; + while ins != Halt { + match ins { + Add(i, j) => { + let v = rs.entry(i).or_insert(0); + *v += 1; + l = j; + }, + Sub(i, j, k) => { + let v = rs.entry(i).or_insert(0); + if *v != 0 { + *v -= 1; + l = j; + } else { + l = k; + } + }, + _ => panic!("unreachable") + } + ins = if l >= program.len() { Halt } else { program[l] }; + } + (l, rs) } // <> = (2^x)*(2y+1) -fn encode_pair1(x: u128, y: u128) -> u128 { - unimplemented!(); +fn encode_pair1(x: Godel, y: Godel) -> Godel { + let left: Godel = Godel::from(2).pow(x.to_u32_wrapping()); + let right: Godel = Godel::from(2) * y + Godel::from(1); + left * right } // = (2^x)*(2y+1)-1 -fn encode_pair2(x: u128, y: u128) -> u128 { - unimplemented!(); +fn encode_pair2(x: Godel, y: Godel) -> Godel { + encode_pair1(x, y) - Godel::from(1) +} +fn encode_list_to_godel(l: &[Godel]) -> Godel { + if l.is_empty() { return Godel::ZERO; } + encode_pair1(l[0].clone(), encode_list_to_godel(&l[1..])) } -fn encode_list_to_godel(l: &[u128]) -> u128 { - unimplemented!(); +fn encode_program_to_list(program: &[Instruction]) -> Vec { + program.iter().map(Instruction::encode_instruction).collect() } -fn encode_program_to_list(program: &[Instruction]) -> Vec { - unimplemented!(); +fn trailing_zeros_in_binary(x: Godel) -> Godel { + let mut b = x; + let mut c = Godel::new(); + if b != 0 { + b = (b.clone() ^ (b - 1)) >> 1; + while b != 0 { + c += 1; + b >>= 1; + } + } + c + // in theory it should be infinity, but rug does not support such calls +} +fn decode_instruction(ins: Godel) -> Instruction { + if ins == 0 { return Halt; } + let (x, y) = decode_pair1(ins); + let i: Godel = x.clone() / 2; + if x % 2 != 0 { + let (j, k) = decode_pair2(y); + Sub(i.try_into().unwrap(), j.try_into().unwrap(), k.try_into().unwrap()) + } else { + Add(i.try_into().unwrap(), y.try_into().unwrap()) + } } // a = (2^x)*(2y+1) -fn decode_pair1(a: u128) -> (u128, u128) { - unimplemented!(); +fn decode_pair1(a: Godel) -> (Godel, Godel) { + let x: Godel = trailing_zeros_in_binary(a.clone()); + let z: Godel = a / Godel::from(2).pow(x.to_u32_wrapping()); + let y: Godel = (z - 1) / 2; + (x, y) } // a = (2^x)*(2y+1)-1 -fn decode_pair2(a: u128) -> (u128, u128) { - unimplemented!(); +fn decode_pair2(a: Godel) -> (Godel, Godel) { + decode_pair1(a + 1) } -fn decode_godel_to_list(g: u128) -> Vec { - unimplemented!(); +fn decode_godel_to_list(g: Godel) -> Vec { + if g == 0 { return Vec::new(); } + let (x, xs) = decode_pair1(g); + let mut gs = vec![x]; + gs.splice(gs.len().., decode_godel_to_list(xs)); + gs } -fn decode_list_to_program(program: &[u128]) -> Vec { - unimplemented!(); +fn decode_list_to_program(program: &[Godel]) -> Vec { + program.iter().map(|x| decode_instruction(x.clone())).collect() } fn main() { @@ -49,30 +121,67 @@ fn main() { mod test { use crate::*; + #[test] + fn halt_encodes_to_godel_zero_num() { + let g = Halt.encode_instruction(); + assert_eq!(g, Godel::ZERO); + } + + #[test] + fn godel_zero_num_decodes_to_halt() { + let ins = decode_instruction(Godel::ZERO); + assert_eq!(ins, Halt); + } + #[test] fn godel_num_to_godel_list() { - let n = 2u128.pow(46) * 20483; - let godel_list = decode_godel_to_list(n); - let true_godel_list = vec![46, 0, 10, 1]; + let n = Godel::from(2).pow(46) * 20483; + let godel_list: Vec = decode_godel_to_list(n); + let true_godel_list: Vec = vec![46, 0, 10, 1].iter().map(|x| Godel::from(x.clone())).collect(); assert_eq!(godel_list, true_godel_list) } + #[test] + fn godel_num_to_godel_list_large_num() { + let n = Godel::from(2).pow(216) * 833; + let godel_list: Vec = decode_godel_to_list(n); + let true_godel_list: Vec = vec![216, 5, 1, 0].iter().map(|x| Godel::from(x.clone())).collect(); + assert_eq!(godel_list, true_godel_list) + } + #[test] fn godel_list_to_godel_num() { - let godel_num = encode_list_to_godel(&[46, 0, 10, 1]); + let true_godel_list: Vec = [46, 0, 10, 1].iter().map(|x| Godel::from(x.clone())).collect(); + let godel_num: Godel = encode_list_to_godel(&true_godel_list); assert_eq!(godel_num, 2u128.pow(46) * 20483) } #[test] fn godel_list_to_program() { - let program = decode_list_to_program(&vec![46, 0, 10, 1]); + let true_godel_list: Vec = [46, 0, 10, 1].iter().map(|x| Godel::from(x.clone())).collect(); + let program = decode_list_to_program(&true_godel_list); assert_eq!(program, vec![Sub(0, 2, 1), Halt, Sub(0, 0, 1), Add(0, 0)]) } + #[test] + fn godel_list_to_program_2() { + let true_godel_list: Vec = [408, 2272, 7, 192, 8064, 144, 0].iter().map(|x| Godel::from(x.clone())).collect(); + let program = decode_list_to_program(&true_godel_list); + assert_eq!(program, vec![Sub(1, 1, 6), Sub(2, 2, 4), Add(0, 3), Add(3, 1), Sub(3, 5, 0), Add(2, 4), Halt]) + } + #[test] fn program_to_godel_list() { - let program = encode_program_to_list(&[Sub(0, 2, 1), Halt, Sub(0, 0, 1), Add(0, 0)]); - assert_eq!(program, [46, 0, 10, 1]) + let program: Vec = encode_program_to_list(&[Sub(0, 2, 1), Halt, Sub(0, 0, 1), Add(0, 0)]); + let true_godel_list: Vec = [46, 0, 10, 1].iter().map(|x| Godel::from(x.clone())).collect(); + assert_eq!(program, true_godel_list) + } + + #[test] + fn program_to_godel_list_2() { + let program: Vec = encode_program_to_list(&[Sub(1, 1, 6), Sub(2, 2, 4), Add(0, 3), Add(3, 1), Sub(3, 5, 0), Add(2, 4), Halt]); + let true_godel_list: Vec = [408, 2272, 7, 192, 8064, 144, 0].iter().map(|x| Godel::from(x.clone())).collect(); + assert_eq!(program, true_godel_list) } #[test] From 99b516d8f3329303edb1d3d0664cedf92ff50b61 Mon Sep 17 00:00:00 2001 From: Khemi Date: Tue, 28 Dec 2021 17:37:11 +0000 Subject: [PATCH 05/12] update state to store unbounded int, and pass references into functions --- register-machines/src/main.rs | 58 +++++++++++++++++------------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/register-machines/src/main.rs b/register-machines/src/main.rs index c1f9720..985ac1e 100644 --- a/register-machines/src/main.rs +++ b/register-machines/src/main.rs @@ -1,10 +1,10 @@ use std::{collections::HashMap, vec}; -use rug::{Integer, ops::Pow}; +use rug::{Integer, ops::Pow, Complete}; type Godel = Integer; type Label = usize; type Register = u128; -type State = (Label, HashMap); +type State = (Label, HashMap); #[derive(Clone, Copy, Debug, PartialEq)] enum Instruction { @@ -17,10 +17,10 @@ impl Instruction { pub fn encode_instruction(&self) -> Godel { match self { Add(i, j) => - encode_pair1(Godel::from(2 * i), Godel::from(*j)), + encode_pair1(&Godel::from(2 * i), &Godel::from(*j)), Sub(i, j, k) => - encode_pair1(Godel::from(2 * i + 1), - encode_pair2(Godel::from(*j), Godel::from(*k)) + encode_pair1(&Godel::from(2 * i + 1), + &encode_pair2(&Godel::from(*j), &Godel::from(*k)) ), Halt => Godel::ZERO } @@ -34,12 +34,12 @@ fn eval_program(program: &[Instruction], state: &State) -> State { while ins != Halt { match ins { Add(i, j) => { - let v = rs.entry(i).or_insert(0); + let v = rs.entry(i).or_insert(Godel::new()); *v += 1; l = j; }, Sub(i, j, k) => { - let v = rs.entry(i).or_insert(0); + let v = rs.entry(i).or_insert(Godel::new()); if *v != 0 { *v -= 1; l = j; @@ -54,24 +54,24 @@ fn eval_program(program: &[Instruction], state: &State) -> State { (l, rs) } // <> = (2^x)*(2y+1) -fn encode_pair1(x: Godel, y: Godel) -> Godel { +fn encode_pair1(x: &Godel, y: &Godel) -> Godel { let left: Godel = Godel::from(2).pow(x.to_u32_wrapping()); let right: Godel = Godel::from(2) * y + Godel::from(1); left * right } // = (2^x)*(2y+1)-1 -fn encode_pair2(x: Godel, y: Godel) -> Godel { +fn encode_pair2(x: &Godel, y: &Godel) -> Godel { encode_pair1(x, y) - Godel::from(1) } fn encode_list_to_godel(l: &[Godel]) -> Godel { if l.is_empty() { return Godel::ZERO; } - encode_pair1(l[0].clone(), encode_list_to_godel(&l[1..])) + encode_pair1(&l[0], &encode_list_to_godel(&l[1..])) } fn encode_program_to_list(program: &[Instruction]) -> Vec { program.iter().map(Instruction::encode_instruction).collect() } -fn trailing_zeros_in_binary(x: Godel) -> Godel { - let mut b = x; +fn trailing_zeros_in_binary(x: &Godel) -> Godel { + let mut b = x.clone(); let mut c = Godel::new(); if b != 0 { b = (b.clone() ^ (b - 1)) >> 1; @@ -83,37 +83,37 @@ fn trailing_zeros_in_binary(x: Godel) -> Godel { c // in theory it should be infinity, but rug does not support such calls } -fn decode_instruction(ins: Godel) -> Instruction { - if ins == 0 { return Halt; } +fn decode_instruction(ins: &Godel) -> Instruction { + if *ins == 0 { return Halt; } let (x, y) = decode_pair1(ins); let i: Godel = x.clone() / 2; if x % 2 != 0 { - let (j, k) = decode_pair2(y); + let (j, k) = decode_pair2(&y); Sub(i.try_into().unwrap(), j.try_into().unwrap(), k.try_into().unwrap()) } else { Add(i.try_into().unwrap(), y.try_into().unwrap()) } } // a = (2^x)*(2y+1) -fn decode_pair1(a: Godel) -> (Godel, Godel) { - let x: Godel = trailing_zeros_in_binary(a.clone()); - let z: Godel = a / Godel::from(2).pow(x.to_u32_wrapping()); +fn decode_pair1(a: &Godel) -> (Godel, Godel) { + let x: Godel = trailing_zeros_in_binary(a); + let z = Godel::from(a >> x.to_u32_wrapping()); let y: Godel = (z - 1) / 2; (x, y) } // a = (2^x)*(2y+1)-1 -fn decode_pair2(a: Godel) -> (Godel, Godel) { - decode_pair1(a + 1) +fn decode_pair2(a: &Godel) -> (Godel, Godel) { + decode_pair1(&Godel::from(a + 1)) } -fn decode_godel_to_list(g: Godel) -> Vec { - if g == 0 { return Vec::new(); } +fn decode_godel_to_list(g: &Godel) -> Vec { + if *g == 0 { return Vec::new(); } let (x, xs) = decode_pair1(g); let mut gs = vec![x]; - gs.splice(gs.len().., decode_godel_to_list(xs)); + gs.splice(gs.len().., decode_godel_to_list(&xs)); gs } fn decode_list_to_program(program: &[Godel]) -> Vec { - program.iter().map(|x| decode_instruction(x.clone())).collect() + program.iter().map(|x| decode_instruction(x)).collect() } fn main() { @@ -129,14 +129,14 @@ mod test { #[test] fn godel_zero_num_decodes_to_halt() { - let ins = decode_instruction(Godel::ZERO); + let ins = decode_instruction(&Godel::ZERO); assert_eq!(ins, Halt); } #[test] fn godel_num_to_godel_list() { let n = Godel::from(2).pow(46) * 20483; - let godel_list: Vec = decode_godel_to_list(n); + let godel_list: Vec = decode_godel_to_list(&n); let true_godel_list: Vec = vec![46, 0, 10, 1].iter().map(|x| Godel::from(x.clone())).collect(); assert_eq!(godel_list, true_godel_list) } @@ -144,7 +144,7 @@ mod test { #[test] fn godel_num_to_godel_list_large_num() { let n = Godel::from(2).pow(216) * 833; - let godel_list: Vec = decode_godel_to_list(n); + let godel_list: Vec = decode_godel_to_list(&n); let true_godel_list: Vec = vec![216, 5, 1, 0].iter().map(|x| Godel::from(x.clone())).collect(); assert_eq!(godel_list, true_godel_list) } @@ -199,14 +199,14 @@ mod test { &program, &( 0, - HashMap::<_, _>::from_iter(IntoIter::new([(0, 0), (1, 7)])) + HashMap::<_, _>::from_iter(IntoIter::new([(0, Godel::from(0)), (1, Godel::from(7))])) ), ); assert_eq!( final_state, ( 4, - HashMap::<_, _>::from_iter(IntoIter::new([(0, 2), (1, 0)])) + HashMap::<_, _>::from_iter(IntoIter::new([(0, Godel::from(2)), (1, Godel::from(0))])) ) ) } From c324b016957c306f2f2bf4dd4221a5d2ed6096bd Mon Sep 17 00:00:00 2001 From: Khemi Date: Wed, 29 Dec 2021 13:55:25 +0000 Subject: [PATCH 06/12] cleanup and optimisation --- register-machines/src/main.rs | 68 ++++++++++++++++------------------- 1 file changed, 30 insertions(+), 38 deletions(-) diff --git a/register-machines/src/main.rs b/register-machines/src/main.rs index 985ac1e..9300dd9 100644 --- a/register-machines/src/main.rs +++ b/register-machines/src/main.rs @@ -1,5 +1,5 @@ use std::{collections::HashMap, vec}; -use rug::{Integer, ops::Pow, Complete}; +use rug::Integer; type Godel = Integer; type Label = usize; @@ -13,29 +13,13 @@ enum Instruction { Halt } -impl Instruction { - pub fn encode_instruction(&self) -> Godel { - match self { - Add(i, j) => - encode_pair1(&Godel::from(2 * i), &Godel::from(*j)), - Sub(i, j, k) => - encode_pair1(&Godel::from(2 * i + 1), - &encode_pair2(&Godel::from(*j), &Godel::from(*k)) - ), - Halt => Godel::ZERO - } - } -} - use Instruction::*; fn eval_program(program: &[Instruction], state: &State) -> State { let (mut l, mut rs) = state.clone(); - let mut ins = program[l]; - while ins != Halt { - match ins { + while l < program.len() { + match program[l] { Add(i, j) => { - let v = rs.entry(i).or_insert(Godel::new()); - *v += 1; + *rs.entry(i).or_insert(Godel::new()) += 1; l = j; }, Sub(i, j, k) => { @@ -47,29 +31,38 @@ fn eval_program(program: &[Instruction], state: &State) -> State { l = k; } }, - _ => panic!("unreachable") + Halt => { break; } } - ins = if l >= program.len() { Halt } else { program[l] }; } (l, rs) } +fn encode_instruction(ins: &Instruction) -> Godel { + match ins { + Add(i, j) => + encode_pair1(&Godel::from(2 * i), &Godel::from(*j)), + Sub(i, j, k) => + encode_pair1(&Godel::from(2 * i + 1), + &encode_pair2(&Godel::from(*j), &Godel::from(*k)) + ), + Halt => Godel::ZERO + } +} // <> = (2^x)*(2y+1) fn encode_pair1(x: &Godel, y: &Godel) -> Godel { - let left: Godel = Godel::from(2).pow(x.to_u32_wrapping()); - let right: Godel = Godel::from(2) * y + Godel::from(1); - left * right + (Godel::from(2) * y + 1) << x.to_u32_wrapping() } // = (2^x)*(2y+1)-1 fn encode_pair2(x: &Godel, y: &Godel) -> Godel { - encode_pair1(x, y) - Godel::from(1) + encode_pair1(x, y) - 1 } fn encode_list_to_godel(l: &[Godel]) -> Godel { if l.is_empty() { return Godel::ZERO; } encode_pair1(&l[0], &encode_list_to_godel(&l[1..])) } fn encode_program_to_list(program: &[Instruction]) -> Vec { - program.iter().map(Instruction::encode_instruction).collect() + program.iter().map(|x| encode_instruction(x)).collect() } +// Returns 0 if there are no trailing zeros, else return trailing zero count (in binary) fn trailing_zeros_in_binary(x: &Godel) -> Godel { let mut b = x.clone(); let mut c = Godel::new(); @@ -81,7 +74,6 @@ fn trailing_zeros_in_binary(x: &Godel) -> Godel { } } c - // in theory it should be infinity, but rug does not support such calls } fn decode_instruction(ins: &Godel) -> Instruction { if *ins == 0 { return Halt; } @@ -123,7 +115,7 @@ mod test { use crate::*; #[test] fn halt_encodes_to_godel_zero_num() { - let g = Halt.encode_instruction(); + let g = encode_instruction(&Halt); assert_eq!(g, Godel::ZERO); } @@ -135,37 +127,37 @@ mod test { #[test] fn godel_num_to_godel_list() { - let n = Godel::from(2).pow(46) * 20483; + let n = Godel::from(20483) << 46; let godel_list: Vec = decode_godel_to_list(&n); - let true_godel_list: Vec = vec![46, 0, 10, 1].iter().map(|x| Godel::from(x.clone())).collect(); + let true_godel_list: Vec = vec![46, 0, 10, 1].iter().map(|x| Godel::from(*x)).collect(); assert_eq!(godel_list, true_godel_list) } #[test] fn godel_num_to_godel_list_large_num() { - let n = Godel::from(2).pow(216) * 833; + let n = Godel::from(833) << 216; let godel_list: Vec = decode_godel_to_list(&n); - let true_godel_list: Vec = vec![216, 5, 1, 0].iter().map(|x| Godel::from(x.clone())).collect(); + let true_godel_list: Vec = vec![216, 5, 1, 0].iter().map(|x| Godel::from(*x)).collect(); assert_eq!(godel_list, true_godel_list) } #[test] fn godel_list_to_godel_num() { - let true_godel_list: Vec = [46, 0, 10, 1].iter().map(|x| Godel::from(x.clone())).collect(); + let true_godel_list: Vec = [46, 0, 10, 1].iter().map(|x| Godel::from(*x)).collect(); let godel_num: Godel = encode_list_to_godel(&true_godel_list); assert_eq!(godel_num, 2u128.pow(46) * 20483) } #[test] fn godel_list_to_program() { - let true_godel_list: Vec = [46, 0, 10, 1].iter().map(|x| Godel::from(x.clone())).collect(); + let true_godel_list: Vec = [46, 0, 10, 1].iter().map(|x| Godel::from(*x)).collect(); let program = decode_list_to_program(&true_godel_list); assert_eq!(program, vec![Sub(0, 2, 1), Halt, Sub(0, 0, 1), Add(0, 0)]) } #[test] fn godel_list_to_program_2() { - let true_godel_list: Vec = [408, 2272, 7, 192, 8064, 144, 0].iter().map(|x| Godel::from(x.clone())).collect(); + let true_godel_list: Vec = [408, 2272, 7, 192, 8064, 144, 0].iter().map(|x| Godel::from(*x)).collect(); let program = decode_list_to_program(&true_godel_list); assert_eq!(program, vec![Sub(1, 1, 6), Sub(2, 2, 4), Add(0, 3), Add(3, 1), Sub(3, 5, 0), Add(2, 4), Halt]) } @@ -173,14 +165,14 @@ mod test { #[test] fn program_to_godel_list() { let program: Vec = encode_program_to_list(&[Sub(0, 2, 1), Halt, Sub(0, 0, 1), Add(0, 0)]); - let true_godel_list: Vec = [46, 0, 10, 1].iter().map(|x| Godel::from(x.clone())).collect(); + let true_godel_list: Vec = [46, 0, 10, 1].iter().map(|x| Godel::from(*x)).collect(); assert_eq!(program, true_godel_list) } #[test] fn program_to_godel_list_2() { let program: Vec = encode_program_to_list(&[Sub(1, 1, 6), Sub(2, 2, 4), Add(0, 3), Add(3, 1), Sub(3, 5, 0), Add(2, 4), Halt]); - let true_godel_list: Vec = [408, 2272, 7, 192, 8064, 144, 0].iter().map(|x| Godel::from(x.clone())).collect(); + let true_godel_list: Vec = [408, 2272, 7, 192, 8064, 144, 0].iter().map(|x| Godel::from(*x)).collect(); assert_eq!(program, true_godel_list) } From db13e136e0a6ba056d208af5bad0c88247e9a383 Mon Sep 17 00:00:00 2001 From: Khemi Date: Tue, 4 Jan 2022 23:20:05 +0000 Subject: [PATCH 07/12] implemented functionality for m,n,k tic tac toe --- tictactoe/Cargo.toml | 3 +- tictactoe/src/main.rs | 268 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 244 insertions(+), 27 deletions(-) diff --git a/tictactoe/Cargo.toml b/tictactoe/Cargo.toml index 0b0eb68..cc7780e 100644 --- a/tictactoe/Cargo.toml +++ b/tictactoe/Cargo.toml @@ -5,4 +5,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[dependencies] \ No newline at end of file +[dependencies] +strum = { version = "0.23", features = ["derive"] } \ No newline at end of file diff --git a/tictactoe/src/main.rs b/tictactoe/src/main.rs index 26b0786..195a520 100644 --- a/tictactoe/src/main.rs +++ b/tictactoe/src/main.rs @@ -1,6 +1,7 @@ -use std::fmt; +use std::{fmt, collections::HashMap}; +use strum::{EnumIter, IntoEnumIterator}; -#[derive(PartialEq, Copy, Clone, Debug)] +#[derive(PartialEq, Copy, Clone, Debug, EnumIter, Eq, Hash)] enum Player { Player1, Player2, @@ -30,6 +31,7 @@ enum GameResult { enum GameError { NotLegalPosition, PositionOccupied, + MoveNotFound } trait Game { @@ -39,20 +41,163 @@ trait Game { fn execute_move(&mut self, player_move: Self::Move) -> Result<(), GameError>; fn undo_move(&mut self, player_move: Self::Move) -> Result<(), GameError>; } - -#[derive(PartialEq, Copy, Clone, Debug)] + +#[derive(PartialEq, Clone, Debug)] struct MNKBoard { + ply: usize, board: [[Option; N]; M], + move_sequence: Vec< as Game>::Move>, current_player: Player } impl MNKBoard { fn new() -> Self { MNKBoard { - board: [[None; N]; M], + ply: 0, + board: [[None; N]; M], + move_sequence: vec![], current_player: Player::Player1 } } + + fn iter2d(&self) -> impl Iterator)> { + self.board.iter() + .enumerate() + .flat_map(|(y, row)| + row.iter().enumerate().map(move |(x, cell)| ((x, y), cell))) + } + + // Check if moves supplied contain at least one K-in-a-row combination for the specified player + // Time complexity: O(L), where L = moves.len() + fn has_win_combination(mut moves: Vec<&Option>, player: Player) -> bool { + if moves.len() < K { + return false; + } + + let mut count: HashMap, usize> = HashMap::new(); + let mut to_remove: usize = 0; + let remainder = moves.split_off(K); + + for &&m in &moves { + *count.entry(m).or_insert(0) += 1; + } + + if *count.entry(Some(player)).or_insert(0) == K { + return true; + } + + for &&m in &remainder { + *count.entry(*moves[to_remove]).or_insert(0) -= 1; + *count.entry(m).or_insert(0) += 1; + to_remove += 1; + if *count.entry(Some(player)).or_insert(0) == K { + return true; + } + } + + false + // moves.windows(K) + // .map(|ms| ms.iter().filter(|&&&m| m == Some(player))) + // .any(|ms| ms.count() == K) + } + + // Check if current move results in the win of the current player + // Time complexity: O(K) + fn check_current_move(&self) -> Option { + let current_move = self.move_sequence.last(); + + if current_move.is_none() { + return None; + } + + let &(r, c) = current_move.unwrap(); + let player = self.board[r][c].unwrap(); + + // Initialise horizontal and vertical ranges + let horizontal = ((c - K + 1)..(c + K)).filter(|col| col + 1 >= K && col + K <= N); + let vertical = ((r - K + 1)..(r + K)).filter(|row| row + 1 >= K && row + K <= M); + + // Check horizontal moves + let horizontal_moves: Vec<_> = horizontal.clone().map(|col| &self.board[r][col]).collect(); + if Self::has_win_combination(horizontal_moves, player) { return Some(GameResult::PlayerWon(player)); } + + // Check vertical moves + let vertical_moves: Vec<_> = vertical.clone().map(|row| &self.board[row][c]).collect(); + if Self::has_win_combination(vertical_moves, player) { return Some(GameResult::PlayerWon(player)); } + + // Check main diagonal moves + let main_diag_moves: Vec<_> = horizontal.clone().zip(vertical.clone()).map(|(row, col)| &self.board[row][col]).collect(); + if Self::has_win_combination(main_diag_moves, player) { return Some(GameResult::PlayerWon(player)); } + + // Check anti diagonal moves + let anti_diag_moves: Vec<_> = horizontal.zip(vertical.rev()).map(|(row, col)| &self.board[row][col]).collect(); + if Self::has_win_combination(anti_diag_moves, player) { return Some(GameResult::PlayerWon(player)); } + + None + } + + // Inspect the entire board to find a winner + // Usage: when a non-trivial board state is preloaded with no move sequence provided + // Time complexity: O(M * N) + fn check_board(&self) -> Option { + // Check horizontal moves + for horizontal_moves in self.board { + for player in Player::iter() { + if Self::has_win_combination(horizontal_moves.iter().collect(), player) { + return Some(GameResult::PlayerWon(player)); + } + } + } + + // Check vertical moves + for col in 0..N { + let vertical_moves: Vec<_> = (0..M).map(|row| &self.board[row][col]).collect(); + for player in Player::iter() { + if Self::has_win_combination(vertical_moves.clone(), player) { + return Some(GameResult::PlayerWon(player)); + } + } + } + + // Initialise diagonal moves + let mut main_diag_moves: Vec>> = vec![vec![]; M + N - 1]; + let mut anti_diag_moves: Vec>> = vec![vec![]; main_diag_moves.len()]; + + for row in 0..M { + for col in 0..N { + main_diag_moves[row + col].push(&self.board[row][col]); + } + } + + // Main diagonals + for diagonal_moves in main_diag_moves { + for player in Player::iter() { + if Self::has_win_combination(diagonal_moves.clone(), player) { + return Some(GameResult::PlayerWon(player)); + } + } + } + + let min_anti_diag = - (M as i32) + 1; + + for row in 0..M { + for col in 0..N { + let index = col as i32 - row as i32 - min_anti_diag; + anti_diag_moves[index as usize].push(&self.board[row][col]); + } + } + + // Anti diagonals + for diagonal_moves in anti_diag_moves { + for player in Player::iter() { + if Self::has_win_combination(diagonal_moves.clone(), player) { + return Some(GameResult::PlayerWon(player)); + } + } + } + + None + } } impl fmt::Display for MNKBoard { @@ -63,20 +208,65 @@ impl fmt::Display for MNKBoard Game for MNKBoard { type Move = (usize, usize); + + // Generate list of legal moves that can be made by a player + // Time complexity: O(M * N) fn moves(&self) -> Vec { - unimplemented!(); + self.iter2d() + .filter(|(_, c)| c.is_none()) + .map(|((r, c), _)| (c, r)) + .collect() } + // Evaluate the game state to determine if there is a winner + // An analysis for this function can be conducted to find amortized time complexity fn winner(&self) -> Option { - unimplemented!(); + if self.ply == M * N { + return Some(GameResult::Draw); + } + + match self.move_sequence.last() { + // Board preloaded with no move sequences given; inspect whole board to determine game result + // Time complexity: O(M * N) + None => { self.check_board() } + // Use last move to determine game result + // Time complexity: O(K) + _ => { self.check_current_move() } + } } - fn execute_move(&mut self, player_move: Self::Move) -> Result<(), GameError> { - unimplemented!(); + fn execute_move(&mut self, (r, c): Self::Move) -> Result<(), GameError> { + if !(0..self.board.len()).contains(&r) || !(0..self.board[0].len()).contains(&c) { + return Err(GameError::NotLegalPosition); + } + if self.board[r][c].is_some() { + return Err(GameError::PositionOccupied); + } + + self.ply += 1; + self.board[r][c] = Some(self.current_player); + Ok(()) } + // Undo all moves starting from player_move if it is a valid move, otherwise throw a MoveNotFound error fn undo_move(&mut self, player_move: Self::Move) -> Result<(), GameError> { - unimplemented!(); + let index = self.move_sequence.iter().position(|&m| m == player_move); + + match index { + None => { Err(GameError::MoveNotFound) }, + Some(pos) => { // ply = pos + 1 + let removed_moves = self.move_sequence.drain(pos..); + removed_moves.into_iter().for_each(|(r, c)| self.board[r][c] = None); + + let removed_ply_count = self.ply - pos; + if removed_ply_count % 2 != 0 { + self.current_player = self.current_player.get_last(); + } + + self.ply = pos - 1; + Ok(()) + } + } } } @@ -135,9 +325,11 @@ mod test { #[test] fn three_by_three_tests() { let board = MNKBoard::<3, 3, 3>{ + ply: 3, board: [[Some(Player1), None, None], - [Some(Player1), None, None], - [Some(Player1), None, None]], + [Some(Player1), None, None], + [Some(Player1), None, None]], + move_sequence: vec![], current_player: Player1 }; @@ -148,54 +340,66 @@ mod test { ); let board = MNKBoard::<3, 3, 3> { + ply: 3, board: [[None, Some(Player2), None], - [None, Some(Player2), None], - [None, Some(Player2), None]], + [None, Some(Player2), None], + [None, Some(Player2), None]], + move_sequence: vec![], current_player: Player1 }; assert_eq!(board.winner(), Some(PlayerWon(Player2))); let board = MNKBoard::<3, 3, 3> { + ply: 3, board: [[None, Some(Player2), None], - [None, Some(Player2), None], - [None, Some(Player1), None]], + [None, Some(Player2), None], + [None, Some(Player1), None]], + move_sequence: vec![], current_player: Player1 }; assert_eq!(board.winner(), None); let board = MNKBoard::<3, 3, 2> { + ply: 3, board: [[None, Some(Player2), None], - [None, Some(Player2), None], - [None, Some(Player1), None]], + [None, Some(Player2), None], + [None, Some(Player1), None]], + move_sequence: vec![], current_player: Player1 }; assert_eq!(board.winner(), Some(PlayerWon(Player2))); let board = MNKBoard::<3, 3, 3> { + ply: 5, board: [[None, Some(Player2), None], - [None, Some(Player1), None], - [Some(Player2), Some(Player2), Some(Player2)]], + [None, Some(Player1), None], + [Some(Player2), Some(Player2), Some(Player2)]], + move_sequence: vec![], current_player: Player1 }; assert_eq!(board.winner(), Some(PlayerWon(Player2))); let board = MNKBoard::<3, 3, 2> { + ply: 4, board: [[None, Some(Player2), None], - [None, Some(Player1), None], - [Some(Player2), None, Some(Player1)]], + [None, Some(Player1), None], + [Some(Player2), None, Some(Player1)]], + move_sequence: vec![], current_player: Player1 }; assert_eq!(board.winner(), Some(PlayerWon(Player1))); let board = MNKBoard::<3, 3, 2> { + ply: 4, board: [[None, Some(Player2), None], - [Some(Player1), None, None], - [Some(Player2), Some(Player1), None]], + [Some(Player1), None, None], + [Some(Player2), Some(Player1), None]], + move_sequence: vec![], current_player: Player1 }; @@ -206,52 +410,64 @@ mod test { ); let board = MNKBoard::<3, 3, 2> { + ply: 4, board: [[None, Some(Player2), None], - [Some(Player1), None, Some(Player2)], - [Some(Player2), None, None]], + [Some(Player1), None, Some(Player2)], + [Some(Player2), None, None]], + move_sequence: vec![], current_player: Player1 }; assert_eq!(board.winner(), Some(PlayerWon(Player2))); let board = MNKBoard::<2, 3, 2> { + ply: 3, board: [[None, Some(Player2), None], [Some(Player1), None, Some(Player2)]], + move_sequence: vec![], current_player: Player1 }; assert_eq!(board.winner(), Some(PlayerWon(Player2))); let board = MNKBoard::<3, 2, 2> { + ply: 4, board: [[None, Some(Player2)], [Some(Player1), None], [Some(Player2), Some(Player1)]], + move_sequence: vec![], current_player: Player1 }; assert_eq!(board.winner(), Some(PlayerWon(Player1))); let board = MNKBoard::<3, 2, 2> { + ply: 4, board: [[None, Some(Player1)], [None, Some(Player2)], [Some(Player2), Some(Player1)]], + move_sequence: vec![], current_player: Player1 }; assert_eq!(board.winner(), Some(PlayerWon(Player2))); let board = MNKBoard::<2, 3, 2> { + ply: 3, board: [[None, Some(Player2), None], [Some(Player2), None, Some(Player1)]], + move_sequence: vec![], current_player: Player1 }; assert_eq!(board.winner(), Some(PlayerWon(Player2))); let board = MNKBoard::<3, 3, 2> { + ply: 2, board: [[None, None, None], [None, None, Some(Player1)], [None, Some(Player1), None]], + move_sequence: vec![], current_player: Player1 }; From c36831780569f4a47ff055051e3b0b0f48ac58a8 Mon Sep 17 00:00:00 2001 From: Khemi Date: Thu, 6 Jan 2022 02:51:48 +0000 Subject: [PATCH 08/12] implement board display, naive minimax, and fix bugs --- tictactoe/Cargo.toml | 4 +- tictactoe/src/main.rs | 370 +++++++++++++++++++++++++++--------------- 2 files changed, 246 insertions(+), 128 deletions(-) diff --git a/tictactoe/Cargo.toml b/tictactoe/Cargo.toml index cc7780e..2ee2d09 100644 --- a/tictactoe/Cargo.toml +++ b/tictactoe/Cargo.toml @@ -6,4 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -strum = { version = "0.23", features = ["derive"] } \ No newline at end of file +strum = { version = "0.23", features = ["derive"] } +itertools = "0.10.2" +colored = "2" diff --git a/tictactoe/src/main.rs b/tictactoe/src/main.rs index 195a520..33bd6d8 100644 --- a/tictactoe/src/main.rs +++ b/tictactoe/src/main.rs @@ -1,4 +1,6 @@ -use std::{fmt, collections::HashMap}; +use colored::*; +use itertools::Itertools; +use std::{cmp, collections::HashMap, fmt}; use strum::{EnumIter, IntoEnumIterator}; #[derive(PartialEq, Copy, Clone, Debug, EnumIter, Eq, Hash)] @@ -31,23 +33,23 @@ enum GameResult { enum GameError { NotLegalPosition, PositionOccupied, - MoveNotFound + MoveNotFound, } trait Game { - type Move; + type Move: Clone; fn moves(&self) -> Vec; fn winner(&self) -> Option; fn execute_move(&mut self, player_move: Self::Move) -> Result<(), GameError>; fn undo_move(&mut self, player_move: Self::Move) -> Result<(), GameError>; } - + #[derive(PartialEq, Clone, Debug)] struct MNKBoard { ply: usize, - board: [[Option; N]; M], - move_sequence: Vec< as Game>::Move>, - current_player: Player + board: [[Option; N]; M], + move_sequence: Vec< as Game>::Move>, + current_player: Player, } impl MNKBoard { @@ -56,41 +58,43 @@ impl MNKBoard { ply: 0, board: [[None; N]; M], move_sequence: vec![], - current_player: Player::Player1 + current_player: Player::Player1, } } fn iter2d(&self) -> impl Iterator)> { - self.board.iter() - .enumerate() - .flat_map(|(y, row)| - row.iter().enumerate().map(move |(x, cell)| ((x, y), cell))) + self.board + .iter() + .enumerate() + .flat_map(|(r, row_cells)| row_cells.iter().enumerate().map(move |(c, cell)| ((r, c), cell))) } - // Check if moves supplied contain at least one K-in-a-row combination for the specified player + // Check if moves supplied contain at least one K-in-a-row combination for the specified player // Time complexity: O(L), where L = moves.len() fn has_win_combination(mut moves: Vec<&Option>, player: Player) -> bool { if moves.len() < K { - return false; + return false; } let mut count: HashMap, usize> = HashMap::new(); - let mut to_remove: usize = 0; + let mut to_remove: usize = 0; let remainder = moves.split_off(K); + // TODO: replace with for each for &&m in &moves { *count.entry(m).or_insert(0) += 1; } if *count.entry(Some(player)).or_insert(0) == K { - return true; + return true; } + // TODO: replace with windows for &&m in &remainder { *count.entry(*moves[to_remove]).or_insert(0) -= 1; *count.entry(m).or_insert(0) += 1; to_remove += 1; - if *count.entry(Some(player)).or_insert(0) == K { + if *count.entry(Some(player)).or_insert(0) == K { return true; } } @@ -111,27 +115,48 @@ impl MNKBoard { } let &(r, c) = current_move.unwrap(); + let (r32, c32) = (r as i32, c as i32); + let (m32, n32, k32) = (M as i32, N as i32, K as i32); let player = self.board[r][c].unwrap(); // Initialise horizontal and vertical ranges - let horizontal = ((c - K + 1)..(c + K)).filter(|col| col + 1 >= K && col + K <= N); - let vertical = ((r - K + 1)..(r + K)).filter(|row| row + 1 >= K && row + K <= M); + let horizontal = ((c32 - k32 + 1)..(c32 + k32)) + .filter(|col| col + 1 >= k32 && col + k32 <= n32) + .map(|v| v as usize); + let vertical = ((r32 - k32 + 1)..(r32 + k32)) + .filter(|row| row + 1 >= k32 && row + k32 <= m32) + .map(|v| v as usize); // Check horizontal moves let horizontal_moves: Vec<_> = horizontal.clone().map(|col| &self.board[r][col]).collect(); - if Self::has_win_combination(horizontal_moves, player) { return Some(GameResult::PlayerWon(player)); } + if Self::has_win_combination(horizontal_moves, player) { + return Some(GameResult::PlayerWon(player)); + } // Check vertical moves let vertical_moves: Vec<_> = vertical.clone().map(|row| &self.board[row][c]).collect(); - if Self::has_win_combination(vertical_moves, player) { return Some(GameResult::PlayerWon(player)); } + if Self::has_win_combination(vertical_moves, player) { + return Some(GameResult::PlayerWon(player)); + } - // Check main diagonal moves - let main_diag_moves: Vec<_> = horizontal.clone().zip(vertical.clone()).map(|(row, col)| &self.board[row][col]).collect(); - if Self::has_win_combination(main_diag_moves, player) { return Some(GameResult::PlayerWon(player)); } + // Check main diagonal moves + let main_diag_moves: Vec<_> = horizontal + .clone() + .zip(vertical.clone()) + .map(|(row, col)| &self.board[row][col]) + .collect(); + if Self::has_win_combination(main_diag_moves, player) { + return Some(GameResult::PlayerWon(player)); + } - // Check anti diagonal moves - let anti_diag_moves: Vec<_> = horizontal.zip(vertical.rev()).map(|(row, col)| &self.board[row][col]).collect(); - if Self::has_win_combination(anti_diag_moves, player) { return Some(GameResult::PlayerWon(player)); } + // Check anti diagonal moves + let anti_diag_moves: Vec<_> = horizontal + .zip(vertical.rev()) + .map(|(row, col)| &self.board[row][col]) + .collect(); + if Self::has_win_combination(anti_diag_moves, player) { + return Some(GameResult::PlayerWon(player)); + } None } @@ -143,23 +168,23 @@ impl MNKBoard { // Check horizontal moves for horizontal_moves in self.board { for player in Player::iter() { - if Self::has_win_combination(horizontal_moves.iter().collect(), player) { - return Some(GameResult::PlayerWon(player)); + if Self::has_win_combination(horizontal_moves.iter().collect(), player) { + return Some(GameResult::PlayerWon(player)); } } } - // Check vertical moves + // Check vertical moves for col in 0..N { let vertical_moves: Vec<_> = (0..M).map(|row| &self.board[row][col]).collect(); for player in Player::iter() { - if Self::has_win_combination(vertical_moves.clone(), player) { - return Some(GameResult::PlayerWon(player)); + if Self::has_win_combination(vertical_moves.clone(), player) { + return Some(GameResult::PlayerWon(player)); } } } - // Initialise diagonal moves + // Initialise diagonal moves let mut main_diag_moves: Vec>> = vec![vec![]; M + N - 1]; let mut anti_diag_moves: Vec>> = vec![vec![]; main_diag_moves.len()]; @@ -169,82 +194,98 @@ impl MNKBoard { } } - // Main diagonals + // Main diagonals for diagonal_moves in main_diag_moves { for player in Player::iter() { - if Self::has_win_combination(diagonal_moves.clone(), player) { - return Some(GameResult::PlayerWon(player)); + if Self::has_win_combination(diagonal_moves.clone(), player) { + return Some(GameResult::PlayerWon(player)); } } } - let min_anti_diag = - (M as i32) + 1; + let min_anti_diag = -(M as i32) + 1; for row in 0..M { for col in 0..N { - let index = col as i32 - row as i32 - min_anti_diag; - anti_diag_moves[index as usize].push(&self.board[row][col]); + let index = col as i32 - row as i32 - min_anti_diag; + anti_diag_moves[index as usize].push(&self.board[row][col]); } } - // Anti diagonals + // Anti diagonals for diagonal_moves in anti_diag_moves { for player in Player::iter() { - if Self::has_win_combination(diagonal_moves.clone(), player) { - return Some(GameResult::PlayerWon(player)); + if Self::has_win_combination(diagonal_moves.clone(), player) { + return Some(GameResult::PlayerWon(player)); } } } - None + None } } impl fmt::Display for MNKBoard { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - unimplemented!(); + let sep_row = format!("{}\n", "-".repeat(4 * N - 1)); + let board = self + .board + .iter() + .map(|row| { + let pretty_row = row.iter().map(|c| match c { + Some(Player::Player1) => " O ".cyan().bold(), + Some(Player::Player2) => " X ".red().bold(), + None => " ".normal(), + }); + format!("{}\n", pretty_row.format("|")) + }) + .format(&sep_row); + write!(f, "{}", board) } } impl Game for MNKBoard { type Move = (usize, usize); - // Generate list of legal moves that can be made by a player + // Generate list of legal moves that can be made by a player // Time complexity: O(M * N) + // TODO: use HashSet to improve time fn moves(&self) -> Vec { self.iter2d() - .filter(|(_, c)| c.is_none()) - .map(|((r, c), _)| (c, r)) - .collect() + .filter(|(_, c)| c.is_none()) + .map(|((r, c), _)| (r, c)) + .collect() } - // Evaluate the game state to determine if there is a winner - // An analysis for this function can be conducted to find amortized time complexity + // Evaluate the game state to determine if there is a winner + // An analysis for this function can be conducted to find amortized time complexity fn winner(&self) -> Option { if self.ply == M * N { return Some(GameResult::Draw); } match self.move_sequence.last() { - // Board preloaded with no move sequences given; inspect whole board to determine game result + // Board preloaded with no move sequences given; inspect whole board to determine game result // Time complexity: O(M * N) - None => { self.check_board() } - // Use last move to determine game result + None => self.check_board(), + // Use last move to determine game result // Time complexity: O(K) - _ => { self.check_current_move() } + _ => self.check_current_move(), } } - fn execute_move(&mut self, (r, c): Self::Move) -> Result<(), GameError> { - if !(0..self.board.len()).contains(&r) || !(0..self.board[0].len()).contains(&c) { + fn execute_move(&mut self, player_move @ (r, c): Self::Move) -> Result<(), GameError> { + if !(0..M).contains(&r) || !(0..N).contains(&c) { return Err(GameError::NotLegalPosition); } if self.board[r][c].is_some() { return Err(GameError::PositionOccupied); } - self.ply += 1; + self.ply += 1; self.board[r][c] = Some(self.current_player); + self.current_player = self.current_player.get_next(); + self.move_sequence.push(player_move); Ok(()) } @@ -253,13 +294,16 @@ impl Game for MNKBoard let index = self.move_sequence.iter().position(|&m| m == player_move); match index { - None => { Err(GameError::MoveNotFound) }, - Some(pos) => { // ply = pos + 1 + None => Err(GameError::MoveNotFound), + Some(pos) => { + // ply = pos + 1 let removed_moves = self.move_sequence.drain(pos..); - removed_moves.into_iter().for_each(|(r, c)| self.board[r][c] = None); + removed_moves + .into_iter() + .for_each(|(r, c)| self.board[r][c] = None); let removed_ply_count = self.ply - pos; - if removed_ply_count % 2 != 0 { + if removed_ply_count % 2 != 0 { self.current_player = self.current_player.get_last(); } @@ -273,11 +317,60 @@ impl Game for MNKBoard struct Minimax; impl Minimax { - fn next_move( - &mut self, - game: &G - ) -> G::Move { - unimplemented!(); + fn evaluate(&self, game: &G, depth: i32) -> i32 { + const WIN_VAL: i32 = i32::MAX; + const LOSE_VAL: i32 = i32::MIN; + match game.winner() { + Some(GameResult::PlayerWon(Player::Player1)) => WIN_VAL - depth, + Some(GameResult::PlayerWon(Player::Player2)) => LOSE_VAL + depth, + _ => 0, + } + } + + fn minimax(&self, game: &mut G, depth: i32, player: Player) -> i32 { + if game.winner().is_some() { + return self.evaluate(game, depth); + } + + match player { + Player::Player1 => { + let mut eval = i32::MIN; + for m in game.moves() { + game.execute_move(m.clone()).unwrap(); + eval = cmp::max(eval, self.minimax(game, depth + 1, Player::Player2)); + game.undo_move(m).unwrap(); + } + return eval; + } + Player::Player2 => { + let mut eval = i32::MAX; + for m in game.moves() { + game.execute_move(m.clone()).unwrap(); + eval = cmp::min(eval, self.minimax(game, depth + 1, Player::Player1)); + game.undo_move(m.clone()).unwrap(); + } + return eval; + } + } + } + + fn next_move(&mut self, game: &mut G) -> G::Move { + let move_evals: Vec<_> = game + .moves() + .iter() + .map(|m| { + game.execute_move(m.clone()).unwrap(); + let eval = self.minimax(game, 0, Player::Player2); + game.undo_move(m.clone()).unwrap(); + (m.clone(), eval) + }) + .collect(); + + let (best_move, _) = move_evals + .iter() + .max_by(|(_, v1), (_, v2)| v1.cmp(v2)) + .unwrap(); + best_move.clone() } } @@ -311,8 +404,8 @@ fn main() { } } Player::Player2 => { - b.execute_move(ai.next_move(&b)) - .unwrap(); + let ai_move = ai.next_move(&mut b); + b.execute_move(ai_move).unwrap(); println!("{}", b); } } @@ -324,13 +417,15 @@ mod test { use crate::{GameResult::*, Player::*, *}; #[test] fn three_by_three_tests() { - let board = MNKBoard::<3, 3, 3>{ + let board = MNKBoard::<3, 3, 3> { ply: 3, - board: [[Some(Player1), None, None], - [Some(Player1), None, None], - [Some(Player1), None, None]], + board: [ + [Some(Player1), None, None], + [Some(Player1), None, None], + [Some(Player1), None, None], + ], move_sequence: vec![], - current_player: Player1 + current_player: Player1, }; assert_eq!(board.winner(), Some(PlayerWon(Player1))); @@ -341,134 +436,155 @@ mod test { let board = MNKBoard::<3, 3, 3> { ply: 3, - board: [[None, Some(Player2), None], - [None, Some(Player2), None], - [None, Some(Player2), None]], + board: [ + [None, Some(Player2), None], + [None, Some(Player2), None], + [None, Some(Player2), None], + ], move_sequence: vec![], - current_player: Player1 + current_player: Player1, }; assert_eq!(board.winner(), Some(PlayerWon(Player2))); let board = MNKBoard::<3, 3, 3> { ply: 3, - board: [[None, Some(Player2), None], - [None, Some(Player2), None], - [None, Some(Player1), None]], + board: [ + [None, Some(Player2), None], + [None, Some(Player2), None], + [None, Some(Player1), None], + ], move_sequence: vec![], - current_player: Player1 + current_player: Player1, }; assert_eq!(board.winner(), None); let board = MNKBoard::<3, 3, 2> { ply: 3, - board: [[None, Some(Player2), None], - [None, Some(Player2), None], - [None, Some(Player1), None]], + board: [ + [None, Some(Player2), None], + [None, Some(Player2), None], + [None, Some(Player1), None], + ], move_sequence: vec![], - current_player: Player1 + current_player: Player1, }; assert_eq!(board.winner(), Some(PlayerWon(Player2))); let board = MNKBoard::<3, 3, 3> { - ply: 5, - board: [[None, Some(Player2), None], - [None, Some(Player1), None], - [Some(Player2), Some(Player2), Some(Player2)]], + ply: 5, + board: [ + [None, Some(Player2), None], + [None, Some(Player1), None], + [Some(Player2), Some(Player2), Some(Player2)], + ], move_sequence: vec![], - current_player: Player1 + current_player: Player1, }; assert_eq!(board.winner(), Some(PlayerWon(Player2))); let board = MNKBoard::<3, 3, 2> { ply: 4, - board: [[None, Some(Player2), None], - [None, Some(Player1), None], - [Some(Player2), None, Some(Player1)]], - move_sequence: vec![], - current_player: Player1 + board: [ + [None, Some(Player2), None], + [None, Some(Player1), None], + [Some(Player2), None, Some(Player1)], + ], + move_sequence: vec![], + current_player: Player1, }; assert_eq!(board.winner(), Some(PlayerWon(Player1))); let board = MNKBoard::<3, 3, 2> { ply: 4, - board: [[None, Some(Player2), None], - [Some(Player1), None, None], - [Some(Player2), Some(Player1), None]], + board: [ + [None, Some(Player2), None], + [Some(Player1), None, None], + [Some(Player2), Some(Player1), None], + ], move_sequence: vec![], - current_player: Player1 + current_player: Player1, }; assert_eq!(board.winner(), Some(PlayerWon(Player1))); - assert_eq!( - board.moves(), - vec![(0, 0), (0, 2), (1, 1), (1, 2), (2, 2)] - ); + assert_eq!(board.moves(), vec![(0, 0), (0, 2), (1, 1), (1, 2), (2, 2)]); let board = MNKBoard::<3, 3, 2> { ply: 4, - board: [[None, Some(Player2), None], - [Some(Player1), None, Some(Player2)], - [Some(Player2), None, None]], + board: [ + [None, Some(Player2), None], + [Some(Player1), None, Some(Player2)], + [Some(Player2), None, None], + ], move_sequence: vec![], - current_player: Player1 + current_player: Player1, }; assert_eq!(board.winner(), Some(PlayerWon(Player2))); let board = MNKBoard::<2, 3, 2> { ply: 3, - board: [[None, Some(Player2), None], - [Some(Player1), None, Some(Player2)]], + board: [ + [None, Some(Player2), None], + [Some(Player1), None, Some(Player2)], + ], move_sequence: vec![], - current_player: Player1 + current_player: Player1, }; assert_eq!(board.winner(), Some(PlayerWon(Player2))); let board = MNKBoard::<3, 2, 2> { ply: 4, - board: [[None, Some(Player2)], - [Some(Player1), None], - [Some(Player2), Some(Player1)]], + board: [ + [None, Some(Player2)], + [Some(Player1), None], + [Some(Player2), Some(Player1)], + ], move_sequence: vec![], - current_player: Player1 + current_player: Player1, }; assert_eq!(board.winner(), Some(PlayerWon(Player1))); let board = MNKBoard::<3, 2, 2> { - ply: 4, - board: [[None, Some(Player1)], - [None, Some(Player2)], - [Some(Player2), Some(Player1)]], + ply: 4, + board: [ + [None, Some(Player1)], + [None, Some(Player2)], + [Some(Player2), Some(Player1)], + ], move_sequence: vec![], - current_player: Player1 + current_player: Player1, }; assert_eq!(board.winner(), Some(PlayerWon(Player2))); let board = MNKBoard::<2, 3, 2> { ply: 3, - board: [[None, Some(Player2), None], - [Some(Player2), None, Some(Player1)]], + board: [ + [None, Some(Player2), None], + [Some(Player2), None, Some(Player1)], + ], move_sequence: vec![], - current_player: Player1 + current_player: Player1, }; assert_eq!(board.winner(), Some(PlayerWon(Player2))); let board = MNKBoard::<3, 3, 2> { - ply: 2, - board: [[None, None, None], - [None, None, Some(Player1)], - [None, Some(Player1), None]], + ply: 2, + board: [ + [None, None, None], + [None, None, Some(Player1)], + [None, Some(Player1), None], + ], move_sequence: vec![], - current_player: Player1 + current_player: Player1, }; assert_eq!(board.winner(), Some(PlayerWon(Player1))); From 6bc54485717fd3b0788442eea786ee70c9a2de24 Mon Sep 17 00:00:00 2001 From: Khemi Date: Thu, 6 Jan 2022 03:44:07 +0000 Subject: [PATCH 09/12] fix check current move and implement alpha beta pruning --- tictactoe/src/main.rs | 112 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 91 insertions(+), 21 deletions(-) diff --git a/tictactoe/src/main.rs b/tictactoe/src/main.rs index 33bd6d8..82c0dc4 100644 --- a/tictactoe/src/main.rs +++ b/tictactoe/src/main.rs @@ -63,10 +63,12 @@ impl MNKBoard { } fn iter2d(&self) -> impl Iterator)> { - self.board - .iter() - .enumerate() - .flat_map(|(r, row_cells)| row_cells.iter().enumerate().map(move |(c, cell)| ((r, c), cell))) + self.board.iter().enumerate().flat_map(|(r, row_cells)| { + row_cells + .iter() + .enumerate() + .map(move |(c, cell)| ((r, c), cell)) + }) } // Check if moves supplied contain at least one K-in-a-row combination for the specified player @@ -109,10 +111,7 @@ impl MNKBoard { // Time complexity: O(K) fn check_current_move(&self) -> Option { let current_move = self.move_sequence.last(); - - if current_move.is_none() { - return None; - } + current_move?; let &(r, c) = current_move.unwrap(); let (r32, c32) = (r as i32, c as i32); @@ -121,10 +120,11 @@ impl MNKBoard { // Initialise horizontal and vertical ranges let horizontal = ((c32 - k32 + 1)..(c32 + k32)) - .filter(|col| col + 1 >= k32 && col + k32 <= n32) + .filter(|&col| col >= 0 && col < n32) .map(|v| v as usize); + let vertical = ((r32 - k32 + 1)..(r32 + k32)) - .filter(|row| row + 1 >= k32 && row + k32 <= m32) + .filter(|&row| row >= 0 && row < m32) .map(|v| v as usize); // Check horizontal moves @@ -317,17 +317,25 @@ impl Game for MNKBoard struct Minimax; impl Minimax { + const WIN_VAL: i32 = i32::MAX; + const LOSE_VAL: i32 = i32::MIN; + fn evaluate(&self, game: &G, depth: i32) -> i32 { - const WIN_VAL: i32 = i32::MAX; - const LOSE_VAL: i32 = i32::MIN; match game.winner() { - Some(GameResult::PlayerWon(Player::Player1)) => WIN_VAL - depth, - Some(GameResult::PlayerWon(Player::Player2)) => LOSE_VAL + depth, + Some(GameResult::PlayerWon(Player::Player1)) => Minimax::WIN_VAL - depth, + Some(GameResult::PlayerWon(Player::Player2)) => Minimax::LOSE_VAL + depth, _ => 0, } } - fn minimax(&self, game: &mut G, depth: i32, player: Player) -> i32 { + fn alphabeta( + &self, + game: &mut G, + depth: i32, + mut alpha: i32, + mut beta: i32, + player: Player, + ) -> i32 { if game.winner().is_some() { return self.evaluate(game, depth); } @@ -337,19 +345,33 @@ impl Minimax { let mut eval = i32::MIN; for m in game.moves() { game.execute_move(m.clone()).unwrap(); - eval = cmp::max(eval, self.minimax(game, depth + 1, Player::Player2)); + eval = cmp::max( + eval, + self.alphabeta(game, depth + 1, alpha, beta, Player::Player2), + ); game.undo_move(m).unwrap(); + if eval >= beta { + break; + } + alpha = cmp::max(alpha, eval); } - return eval; + eval } Player::Player2 => { let mut eval = i32::MAX; for m in game.moves() { game.execute_move(m.clone()).unwrap(); - eval = cmp::min(eval, self.minimax(game, depth + 1, Player::Player1)); + eval = cmp::min( + eval, + self.alphabeta(game, depth + 1, alpha, beta, Player::Player1), + ); game.undo_move(m.clone()).unwrap(); + if eval <= alpha { + break; + } + beta = cmp::min(beta, eval); } - return eval; + eval } } } @@ -360,7 +382,13 @@ impl Minimax { .iter() .map(|m| { game.execute_move(m.clone()).unwrap(); - let eval = self.minimax(game, 0, Player::Player2); + let eval = self.alphabeta( + game, + 0, + Minimax::LOSE_VAL, + Minimax::WIN_VAL, + Player::Player2, + ); game.undo_move(m.clone()).unwrap(); (m.clone(), eval) }) @@ -383,7 +411,7 @@ fn parse_input(s: &str) -> Result<(usize, usize), Box> { } fn main() { - let mut b: MNKBoard<4, 4, 4> = MNKBoard::new(); + let mut b: MNKBoard<3, 3, 3> = MNKBoard::new(); let mut ai = Minimax; let mut player = Player::Player1; println!("{}", b); @@ -589,4 +617,46 @@ mod test { assert_eq!(board.winner(), Some(PlayerWon(Player1))); } + + #[test] + fn check_result_by_last_move() { + let board = MNKBoard::<3, 3, 3> { + ply: 5, + board: [ + [Some(Player1), Some(Player1), Some(Player1)], + [None, Some(Player2), None], + [None, Some(Player2), None], + ], + move_sequence: vec![(0, 0)], + current_player: Player2, + }; + + assert_eq!(board.winner(), Some(PlayerWon(Player1))); + + let board = MNKBoard::<3, 3, 3> { + ply: 5, + board: [ + [Some(Player1), Some(Player1), Some(Player1)], + [None, Some(Player2), None], + [None, Some(Player2), None], + ], + move_sequence: vec![(0, 1)], + current_player: Player2, + }; + + assert_eq!(board.winner(), Some(PlayerWon(Player1))); + + let board = MNKBoard::<3, 3, 3> { + ply: 5, + board: [ + [Some(Player1), Some(Player1), Some(Player1)], + [None, Some(Player2), None], + [None, Some(Player2), None], + ], + move_sequence: vec![(0, 2)], + current_player: Player2, + }; + + assert_eq!(board.winner(), Some(PlayerWon(Player1))); + } } From 3a641cede4af44219833c867719e236076e4179e Mon Sep 17 00:00:00 2001 From: Khemi Date: Thu, 6 Jan 2022 15:10:28 +0000 Subject: [PATCH 10/12] correct ply number and code cleanup --- tictactoe/Cargo.toml | 2 +- tictactoe/src/main.rs | 66 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/tictactoe/Cargo.toml b/tictactoe/Cargo.toml index 2ee2d09..110aac7 100644 --- a/tictactoe/Cargo.toml +++ b/tictactoe/Cargo.toml @@ -8,4 +8,4 @@ edition = "2021" [dependencies] strum = { version = "0.23", features = ["derive"] } itertools = "0.10.2" -colored = "2" +colored = "2" \ No newline at end of file diff --git a/tictactoe/src/main.rs b/tictactoe/src/main.rs index 82c0dc4..79401de 100644 --- a/tictactoe/src/main.rs +++ b/tictactoe/src/main.rs @@ -82,7 +82,6 @@ impl MNKBoard { let mut to_remove: usize = 0; let remainder = moves.split_off(K); - // TODO: replace with for each for &&m in &moves { *count.entry(m).or_insert(0) += 1; } @@ -91,7 +90,6 @@ impl MNKBoard { return true; } - // TODO: replace with windows for &&m in &remainder { *count.entry(*moves[to_remove]).or_insert(0) -= 1; *count.entry(m).or_insert(0) += 1; @@ -102,9 +100,6 @@ impl MNKBoard { } false - // moves.windows(K) - // .map(|ms| ms.iter().filter(|&&&m| m == Some(player))) - // .any(|ms| ms.count() == K) } // Check if current move results in the win of the current player @@ -249,7 +244,6 @@ impl Game for MNKBoard // Generate list of legal moves that can be made by a player // Time complexity: O(M * N) - // TODO: use HashSet to improve time fn moves(&self) -> Vec { self.iter2d() .filter(|(_, c)| c.is_none()) @@ -307,7 +301,7 @@ impl Game for MNKBoard self.current_player = self.current_player.get_last(); } - self.ply = pos - 1; + self.ply = pos; Ok(()) } } @@ -322,8 +316,8 @@ impl Minimax { fn evaluate(&self, game: &G, depth: i32) -> i32 { match game.winner() { - Some(GameResult::PlayerWon(Player::Player1)) => Minimax::WIN_VAL - depth, - Some(GameResult::PlayerWon(Player::Player2)) => Minimax::LOSE_VAL + depth, + Some(GameResult::PlayerWon(Player::Player1)) => Minimax::WIN_VAL, + Some(GameResult::PlayerWon(Player::Player2)) => Minimax::LOSE_VAL, _ => 0, } } @@ -341,6 +335,7 @@ impl Minimax { } match player { + // maximising player Player::Player1 => { let mut eval = i32::MIN; for m in game.moves() { @@ -357,6 +352,7 @@ impl Minimax { } eval } + // minimising player Player::Player2 => { let mut eval = i32::MAX; for m in game.moves() { @@ -385,9 +381,9 @@ impl Minimax { let eval = self.alphabeta( game, 0, - Minimax::LOSE_VAL, - Minimax::WIN_VAL, - Player::Player2, + i32::MIN, + i32::MAX, + Player::Player1, ); game.undo_move(m.clone()).unwrap(); (m.clone(), eval) @@ -396,7 +392,7 @@ impl Minimax { let (best_move, _) = move_evals .iter() - .max_by(|(_, v1), (_, v2)| v1.cmp(v2)) + .min_by(|(_, v1), (_, v2)| v1.cmp(v2)) .unwrap(); best_move.clone() } @@ -411,13 +407,14 @@ fn parse_input(s: &str) -> Result<(usize, usize), Box> { } fn main() { - let mut b: MNKBoard<3, 3, 3> = MNKBoard::new(); + let mut b: MNKBoard<4, 4, 4> = MNKBoard::new(); let mut ai = Minimax; let mut player = Player::Player1; println!("{}", b); while b.winner().is_none() { match player { Player::Player1 => { + assert_eq!(b.current_player, Player::Player1); let mut line = String::new(); std::io::stdin().read_line(&mut line).unwrap(); if let Ok(pos) = parse_input(line.as_str()) { @@ -432,6 +429,7 @@ fn main() { } } Player::Player2 => { + assert_eq!(b.current_player, Player::Player2); let ai_move = ai.next_move(&mut b); b.execute_move(ai_move).unwrap(); println!("{}", b); @@ -439,6 +437,13 @@ fn main() { } player = player.get_next(); } + + match b.winner() { + Some(GameResult::PlayerWon(Player::Player1)) => { println!("The winner is: {}", "O".blue().bold()); } + Some(GameResult::PlayerWon(Player::Player2)) => { println!("The winner is: {}", "X".red().bold()); } + Some(GameResult::Draw) => { println!("The game is drawn!"); } + None => {} + } } mod test { @@ -659,4 +664,37 @@ mod test { assert_eq!(board.winner(), Some(PlayerWon(Player1))); } + + #[test] + fn check_current_player_cycle() { + let mut board = MNKBoard::<3, 3, 3>::new(); + assert_eq!(board.current_player, Player1); + board.execute_move((0, 1)).unwrap(); + + assert_eq!(board.current_player, Player2); + board.execute_move((0, 0)).unwrap(); + + assert_eq!(board.current_player, Player1); + board.execute_move((1, 1)).unwrap(); + + assert_eq!(board.current_player, Player2); + board.execute_move((2, 1)).unwrap(); + + assert_eq!(board.current_player, Player1); + board.execute_move((2, 0)).unwrap(); + + assert_eq!(board.current_player, Player2); + board.execute_move((1, 0)).unwrap(); + + assert_eq!(board.current_player, Player1); + board.execute_move((1, 2)).unwrap(); + + assert_eq!(board.current_player, Player2); + board.execute_move((0, 2)).unwrap(); + + assert_eq!(board.current_player, Player1); + board.execute_move((2, 2)).unwrap(); + + assert_eq!(board.winner(), Some(Draw)); + } } From fd1165b848dbca398e7d0ca1bdab070393d6d491 Mon Sep 17 00:00:00 2001 From: Khemi Date: Sun, 9 Jan 2022 02:36:50 +0000 Subject: [PATCH 11/12] introduce zobrist hashing for transposition table --- tictactoe/Cargo.toml | 3 +- tictactoe/src/main.rs | 414 +++++++++++++++++++++++------------------- 2 files changed, 232 insertions(+), 185 deletions(-) diff --git a/tictactoe/Cargo.toml b/tictactoe/Cargo.toml index 110aac7..b9b1487 100644 --- a/tictactoe/Cargo.toml +++ b/tictactoe/Cargo.toml @@ -8,4 +8,5 @@ edition = "2021" [dependencies] strum = { version = "0.23", features = ["derive"] } itertools = "0.10.2" -colored = "2" \ No newline at end of file +colored = "2" +rand = "0.8.0" \ No newline at end of file diff --git a/tictactoe/src/main.rs b/tictactoe/src/main.rs index 79401de..47bcc2b 100644 --- a/tictactoe/src/main.rs +++ b/tictactoe/src/main.rs @@ -1,9 +1,14 @@ use colored::*; use itertools::Itertools; -use std::{cmp, collections::HashMap, fmt}; -use strum::{EnumIter, IntoEnumIterator}; - -#[derive(PartialEq, Copy, Clone, Debug, EnumIter, Eq, Hash)] +use rand::Rng; +use std::{ + cmp, + collections::{HashMap, HashSet}, + fmt, +}; +use strum::{EnumCount, EnumIter, IntoEnumIterator}; + +#[derive(PartialEq, Copy, Clone, Debug, EnumIter, EnumCount, Eq, Hash)] enum Player { Player1, Player2, @@ -44,12 +49,15 @@ trait Game { fn undo_move(&mut self, player_move: Self::Move) -> Result<(), GameError>; } -#[derive(PartialEq, Clone, Debug)] +#[derive(Clone, Debug)] struct MNKBoard { ply: usize, board: [[Option; N]; M], move_sequence: Vec< as Game>::Move>, current_player: Player, + hash: u64, + hash_gen: MNKZobrist, + transposition_table: HashMap>>, } impl MNKBoard { @@ -59,6 +67,34 @@ impl MNKBoard { board: [[None; N]; M], move_sequence: vec![], current_player: Player::Player1, + hash: 0, + hash_gen: MNKZobrist::new(), + transposition_table: HashMap::new(), + } + } + + fn load_board(board: [[Option; N]; M], current_player: Player) -> Self { + let hash_gen = MNKZobrist::::new(); + let mut hash = 0; + let mut ply = 0; + + for (r, row) in board.iter().enumerate() { + for (c, cell) in row.iter().enumerate() { + if let Some(p) = cell { + hash ^= hash_gen.get_hash(*p, r, c); + ply += 1; + } + } + } + + MNKBoard { + ply: ply, + board: board, + move_sequence: vec![], + current_player: current_player, + hash: hash, + hash_gen: hash_gen, + transposition_table: HashMap::new(), } } @@ -278,6 +314,7 @@ impl Game for MNKBoard self.ply += 1; self.board[r][c] = Some(self.current_player); + self.hash ^= self.hash_gen.get_hash(self.current_player, r, c); self.current_player = self.current_player.get_next(); self.move_sequence.push(player_move); Ok(()) @@ -292,29 +329,83 @@ impl Game for MNKBoard Some(pos) => { // ply = pos + 1 let removed_moves = self.move_sequence.drain(pos..); - removed_moves - .into_iter() - .for_each(|(r, c)| self.board[r][c] = None); - let removed_ply_count = self.ply - pos; if removed_ply_count % 2 != 0 { self.current_player = self.current_player.get_last(); } - self.ply = pos; + + // TODO: check hash validity + let cur_player = self.current_player.get_next(); + for (r, c) in removed_moves.into_iter() { + self.board[r][c] = None; + self.hash ^= self.hash_gen.get_hash(cur_player, r, c); + } + Ok(()) } } } } +trait Zobrist { + type Hash; + fn get_hash(&self, player: Player, row: usize, col: usize) -> u64; +} + +#[derive(Clone, Debug)] +struct MNKZobrist { + hash_values: [[[::Hash; N]; M]; Player::COUNT], +} + +impl MNKZobrist { + fn new() -> Self { + let mut rng = rand::thread_rng(); + let mut values: HashSet = HashSet::new(); + while values.len() != Player::COUNT * M * N { + values.insert(rng.gen()); + } + + let values = Vec::from_iter(values); + let mut i = 0; + + let mut table = [[[0; N]; M]; 2]; + for board in table.iter_mut() { + for row in board.iter_mut() { + for cell in row.iter_mut() { + *cell = values[i]; + i += 1; + } + } + } + + MNKZobrist { hash_values: table } + } +} + +impl Zobrist for MNKZobrist { + type Hash = u64; + + fn get_hash(&self, player: Player, row: usize, col: usize) -> Self::Hash { + let pid = player as usize; + self.hash_values[pid][row][col] + } +} + +#[derive(Clone, Debug)] +struct EvalEntry { + depth: usize, + eval: i32, + best_move: T::Move, +} + struct Minimax; impl Minimax { const WIN_VAL: i32 = i32::MAX; const LOSE_VAL: i32 = i32::MIN; - fn evaluate(&self, game: &G, depth: i32) -> i32 { + fn evaluate(&self, game: &G, depth: usize) -> i32 { match game.winner() { Some(GameResult::PlayerWon(Player::Player1)) => Minimax::WIN_VAL, Some(GameResult::PlayerWon(Player::Player2)) => Minimax::LOSE_VAL, @@ -325,7 +416,7 @@ impl Minimax { fn alphabeta( &self, game: &mut G, - depth: i32, + depth: usize, mut alpha: i32, mut beta: i32, player: Player, @@ -335,7 +426,7 @@ impl Minimax { } match player { - // maximising player + // maximising player Player::Player1 => { let mut eval = i32::MIN; for m in game.moves() { @@ -352,7 +443,7 @@ impl Minimax { } eval } - // minimising player + // minimising player Player::Player2 => { let mut eval = i32::MAX; for m in game.moves() { @@ -378,13 +469,7 @@ impl Minimax { .iter() .map(|m| { game.execute_move(m.clone()).unwrap(); - let eval = self.alphabeta( - game, - 0, - i32::MIN, - i32::MAX, - Player::Player1, - ); + let eval = self.alphabeta(game, 0, i32::MIN, i32::MAX, Player::Player1); game.undo_move(m.clone()).unwrap(); (m.clone(), eval) }) @@ -439,9 +524,15 @@ fn main() { } match b.winner() { - Some(GameResult::PlayerWon(Player::Player1)) => { println!("The winner is: {}", "O".blue().bold()); } - Some(GameResult::PlayerWon(Player::Player2)) => { println!("The winner is: {}", "X".red().bold()); } - Some(GameResult::Draw) => { println!("The game is drawn!"); } + Some(GameResult::PlayerWon(Player::Player1)) => { + println!("The winner is: {}", "O".blue().bold()); + } + Some(GameResult::PlayerWon(Player::Player2)) => { + println!("The winner is: {}", "X".red().bold()); + } + Some(GameResult::Draw) => { + println!("The game is drawn!"); + } None => {} } } @@ -450,16 +541,13 @@ mod test { use crate::{GameResult::*, Player::*, *}; #[test] fn three_by_three_tests() { - let board = MNKBoard::<3, 3, 3> { - ply: 3, - board: [ - [Some(Player1), None, None], - [Some(Player1), None, None], - [Some(Player1), None, None], - ], - move_sequence: vec![], - current_player: Player1, - }; + let board = [ + [Some(Player1), None, None], + [Some(Player1), None, None], + [Some(Player1), None, None], + ]; + + let board = MNKBoard::<3, 3, 3>::load_board(board, Player1); assert_eq!(board.winner(), Some(PlayerWon(Player1))); assert_eq!( @@ -467,205 +555,163 @@ mod test { vec![(0, 1), (0, 2), (1, 1), (1, 2), (2, 1), (2, 2)] ); - let board = MNKBoard::<3, 3, 3> { - ply: 3, - board: [ - [None, Some(Player2), None], - [None, Some(Player2), None], - [None, Some(Player2), None], - ], - move_sequence: vec![], - current_player: Player1, - }; + let board = [ + [None, Some(Player2), None], + [None, Some(Player2), None], + [None, Some(Player2), None], + ]; + + let board = MNKBoard::<3, 3, 3>::load_board(board, Player1); assert_eq!(board.winner(), Some(PlayerWon(Player2))); - let board = MNKBoard::<3, 3, 3> { - ply: 3, - board: [ - [None, Some(Player2), None], - [None, Some(Player2), None], - [None, Some(Player1), None], - ], - move_sequence: vec![], - current_player: Player1, - }; + let board = [ + [None, Some(Player2), None], + [None, Some(Player2), None], + [None, Some(Player1), None], + ]; + + let board = MNKBoard::<3, 3, 3>::load_board(board, Player1); assert_eq!(board.winner(), None); - let board = MNKBoard::<3, 3, 2> { - ply: 3, - board: [ - [None, Some(Player2), None], - [None, Some(Player2), None], - [None, Some(Player1), None], - ], - move_sequence: vec![], - current_player: Player1, - }; + let board = [ + [None, Some(Player2), None], + [None, Some(Player2), None], + [None, Some(Player1), None], + ]; + + let board = MNKBoard::<3, 3, 3>::load_board(board, Player1); assert_eq!(board.winner(), Some(PlayerWon(Player2))); - let board = MNKBoard::<3, 3, 3> { - ply: 5, - board: [ - [None, Some(Player2), None], - [None, Some(Player1), None], - [Some(Player2), Some(Player2), Some(Player2)], - ], - move_sequence: vec![], - current_player: Player1, - }; + let board = [ + [None, Some(Player2), None], + [None, Some(Player1), None], + [Some(Player2), Some(Player2), Some(Player2)], + ]; + + let board = MNKBoard::<3, 3, 3>::load_board(board, Player1); assert_eq!(board.winner(), Some(PlayerWon(Player2))); - let board = MNKBoard::<3, 3, 2> { - ply: 4, - board: [ - [None, Some(Player2), None], - [None, Some(Player1), None], - [Some(Player2), None, Some(Player1)], - ], - move_sequence: vec![], - current_player: Player1, - }; + let board = [ + [None, Some(Player2), None], + [None, Some(Player1), None], + [Some(Player2), None, Some(Player1)], + ]; + + let board = MNKBoard::<3, 3, 2>::load_board(board, Player1); assert_eq!(board.winner(), Some(PlayerWon(Player1))); - let board = MNKBoard::<3, 3, 2> { - ply: 4, - board: [ - [None, Some(Player2), None], - [Some(Player1), None, None], - [Some(Player2), Some(Player1), None], - ], - move_sequence: vec![], - current_player: Player1, - }; + let board = [ + [None, Some(Player2), None], + [Some(Player1), None, None], + [Some(Player2), Some(Player1), None], + ]; + + let board = MNKBoard::<3, 3, 2>::load_board(board, Player1); assert_eq!(board.winner(), Some(PlayerWon(Player1))); assert_eq!(board.moves(), vec![(0, 0), (0, 2), (1, 1), (1, 2), (2, 2)]); - let board = MNKBoard::<3, 3, 2> { - ply: 4, - board: [ - [None, Some(Player2), None], - [Some(Player1), None, Some(Player2)], - [Some(Player2), None, None], - ], - move_sequence: vec![], - current_player: Player1, - }; + let board = [ + [None, Some(Player2), None], + [Some(Player1), None, Some(Player2)], + [Some(Player2), None, None], + ]; + + let board = MNKBoard::<3, 3, 2>::load_board(board, Player1); assert_eq!(board.winner(), Some(PlayerWon(Player2))); - let board = MNKBoard::<2, 3, 2> { - ply: 3, - board: [ - [None, Some(Player2), None], - [Some(Player1), None, Some(Player2)], - ], - move_sequence: vec![], - current_player: Player1, - }; + let board = [ + [None, Some(Player2), None], + [Some(Player1), None, Some(Player2)], + ]; + + let board = MNKBoard::<2, 3, 2>::load_board(board, Player1); assert_eq!(board.winner(), Some(PlayerWon(Player2))); - let board = MNKBoard::<3, 2, 2> { - ply: 4, - board: [ - [None, Some(Player2)], - [Some(Player1), None], - [Some(Player2), Some(Player1)], - ], - move_sequence: vec![], - current_player: Player1, - }; + let board = [ + [None, Some(Player2)], + [Some(Player1), None], + [Some(Player2), Some(Player1)], + ]; + + let board = MNKBoard::<3, 2, 2>::load_board(board, Player1); assert_eq!(board.winner(), Some(PlayerWon(Player1))); - let board = MNKBoard::<3, 2, 2> { - ply: 4, - board: [ - [None, Some(Player1)], - [None, Some(Player2)], - [Some(Player2), Some(Player1)], - ], - move_sequence: vec![], - current_player: Player1, - }; + let board = [ + [None, Some(Player1)], + [None, Some(Player2)], + [Some(Player2), Some(Player1)], + ]; + + let board = MNKBoard::<3, 2, 2>::load_board(board, Player1); assert_eq!(board.winner(), Some(PlayerWon(Player2))); - let board = MNKBoard::<2, 3, 2> { - ply: 3, - board: [ - [None, Some(Player2), None], - [Some(Player2), None, Some(Player1)], - ], - move_sequence: vec![], - current_player: Player1, - }; + let board = [ + [None, Some(Player2), None], + [Some(Player2), None, Some(Player1)], + ]; + + let board = MNKBoard::<2, 3, 2>::load_board(board, Player1); assert_eq!(board.winner(), Some(PlayerWon(Player2))); - let board = MNKBoard::<3, 3, 2> { - ply: 2, - board: [ - [None, None, None], - [None, None, Some(Player1)], - [None, Some(Player1), None], - ], - move_sequence: vec![], - current_player: Player1, - }; + let board = [ + [None, None, None], + [None, None, Some(Player1)], + [None, Some(Player1), None], + ]; + + let board = MNKBoard::<3, 3, 2>::load_board(board, Player1); assert_eq!(board.winner(), Some(PlayerWon(Player1))); } #[test] fn check_result_by_last_move() { - let board = MNKBoard::<3, 3, 3> { - ply: 5, - board: [ - [Some(Player1), Some(Player1), Some(Player1)], - [None, Some(Player2), None], - [None, Some(Player2), None], - ], - move_sequence: vec![(0, 0)], - current_player: Player2, - }; + let board = [ + [Some(Player1), Some(Player1), Some(Player1)], + [None, Some(Player2), None], + [None, Some(Player2), None], + ]; + + let mut board = MNKBoard::<3, 3, 3>::load_board(board, Player2); + board.move_sequence = vec![(0, 0)]; assert_eq!(board.winner(), Some(PlayerWon(Player1))); - let board = MNKBoard::<3, 3, 3> { - ply: 5, - board: [ - [Some(Player1), Some(Player1), Some(Player1)], - [None, Some(Player2), None], - [None, Some(Player2), None], - ], - move_sequence: vec![(0, 1)], - current_player: Player2, - }; + let board = [ + [Some(Player1), Some(Player1), Some(Player1)], + [None, Some(Player2), None], + [None, Some(Player2), None], + ]; + + let mut board = MNKBoard::<3, 3, 3>::load_board(board, Player2); + board.move_sequence = vec![(0, 1)]; assert_eq!(board.winner(), Some(PlayerWon(Player1))); - let board = MNKBoard::<3, 3, 3> { - ply: 5, - board: [ - [Some(Player1), Some(Player1), Some(Player1)], - [None, Some(Player2), None], - [None, Some(Player2), None], - ], - move_sequence: vec![(0, 2)], - current_player: Player2, - }; + let board = [ + [Some(Player1), Some(Player1), Some(Player1)], + [None, Some(Player2), None], + [None, Some(Player2), None], + ]; + + let mut board = MNKBoard::<3, 3, 3>::load_board(board, Player2); + board.move_sequence = vec![(0, 2)]; assert_eq!(board.winner(), Some(PlayerWon(Player1))); } - #[test] + #[test] fn check_current_player_cycle() { let mut board = MNKBoard::<3, 3, 3>::new(); assert_eq!(board.current_player, Player1); @@ -673,7 +719,7 @@ mod test { assert_eq!(board.current_player, Player2); board.execute_move((0, 0)).unwrap(); - + assert_eq!(board.current_player, Player1); board.execute_move((1, 1)).unwrap(); From b4fd588bee2a222b7901d86978749ad34447b57c Mon Sep 17 00:00:00 2001 From: Khemi Date: Mon, 10 Jan 2022 02:12:00 +0000 Subject: [PATCH 12/12] introduce use of transposition table, correct functionality of check winner method, and extra tests introduced --- tictactoe/src/main.rs | 288 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 233 insertions(+), 55 deletions(-) diff --git a/tictactoe/src/main.rs b/tictactoe/src/main.rs index 47bcc2b..fdbfb7b 100644 --- a/tictactoe/src/main.rs +++ b/tictactoe/src/main.rs @@ -5,6 +5,7 @@ use std::{ cmp, collections::{HashMap, HashSet}, fmt, + io::Write, }; use strum::{EnumCount, EnumIter, IntoEnumIterator}; @@ -43,6 +44,7 @@ enum GameError { trait Game { type Move: Clone; + type Hash; fn moves(&self) -> Vec; fn winner(&self) -> Option; fn execute_move(&mut self, player_move: Self::Move) -> Result<(), GameError>; @@ -56,8 +58,7 @@ struct MNKBoard { move_sequence: Vec< as Game>::Move>, current_player: Player, hash: u64, - hash_gen: MNKZobrist, - transposition_table: HashMap>>, + transposition: MNKTranspositionTable, } impl MNKBoard { @@ -68,8 +69,7 @@ impl MNKBoard { move_sequence: vec![], current_player: Player::Player1, hash: 0, - hash_gen: MNKZobrist::new(), - transposition_table: HashMap::new(), + transposition: MNKTranspositionTable::new(), } } @@ -88,13 +88,12 @@ impl MNKBoard { } MNKBoard { - ply: ply, - board: board, + ply, + board, move_sequence: vec![], - current_player: current_player, - hash: hash, - hash_gen: hash_gen, - transposition_table: HashMap::new(), + current_player, + hash, + transposition: MNKTranspositionTable::new(), } } @@ -189,7 +188,11 @@ impl MNKBoard { return Some(GameResult::PlayerWon(player)); } - None + if self.ply == M * N { + Some(GameResult::Draw) + } else { + None + } } // Inspect the entire board to find a winner @@ -252,7 +255,11 @@ impl MNKBoard { } } - None + if self.ply == M * N { + Some(GameResult::Draw) + } else { + None + } } } @@ -277,6 +284,7 @@ impl fmt::Display for MNKBoard Game for MNKBoard { type Move = (usize, usize); + type Hash = u64; // Generate list of legal moves that can be made by a player // Time complexity: O(M * N) @@ -290,10 +298,6 @@ impl Game for MNKBoard // Evaluate the game state to determine if there is a winner // An analysis for this function can be conducted to find amortized time complexity fn winner(&self) -> Option { - if self.ply == M * N { - return Some(GameResult::Draw); - } - match self.move_sequence.last() { // Board preloaded with no move sequences given; inspect whole board to determine game result // Time complexity: O(M * N) @@ -314,7 +318,10 @@ impl Game for MNKBoard self.ply += 1; self.board[r][c] = Some(self.current_player); - self.hash ^= self.hash_gen.get_hash(self.current_player, r, c); + self.hash ^= self + .transposition + .hash_func + .get_hash(self.current_player, r, c); self.current_player = self.current_player.get_next(); self.move_sequence.push(player_move); Ok(()) @@ -335,11 +342,15 @@ impl Game for MNKBoard } self.ply = pos; - // TODO: check hash validity - let cur_player = self.current_player.get_next(); + let mut cur_player = self.current_player; for (r, c) in removed_moves.into_iter() { self.board[r][c] = None; - self.hash ^= self.hash_gen.get_hash(cur_player, r, c); + println!( + "{}", + self.transposition.hash_func.get_hash(cur_player, r, c) + ); + self.hash ^= self.transposition.hash_func.get_hash(cur_player, r, c); + cur_player = cur_player.get_next(); } Ok(()) @@ -348,14 +359,9 @@ impl Game for MNKBoard } } -trait Zobrist { - type Hash; - fn get_hash(&self, player: Player, row: usize, col: usize) -> u64; -} - #[derive(Clone, Debug)] struct MNKZobrist { - hash_values: [[[::Hash; N]; M]; Player::COUNT], + hash_values: [[[ as Game>::Hash; N]; M]; Player::COUNT], } impl MNKZobrist { @@ -383,95 +389,172 @@ impl MNKZobrist { } } -impl Zobrist for MNKZobrist { - type Hash = u64; - - fn get_hash(&self, player: Player, row: usize, col: usize) -> Self::Hash { +impl MNKZobrist { + fn get_hash( + &self, + player: Player, + row: usize, + col: usize, + ) -> as Game>::Hash { let pid = player as usize; self.hash_values[pid][row][col] } } +#[derive(Clone, Debug)] +struct MNKTranspositionTable { + table: HashMap< as Game>::Hash, EvalEntry>>, + hash_func: MNKZobrist, +} + +impl MNKTranspositionTable { + fn new() -> Self { + MNKTranspositionTable { + table: HashMap::new(), + hash_func: MNKZobrist::::new(), + } + } + + fn get_entry( + &self, + hash: & as Game>::Hash, + ) -> Option<&EvalEntry>> { + self.table.get(hash) + } + + fn update_eval( + &mut self, + hash: as Game>::Hash, + entry: EvalEntry>, + ) { + self.table.insert(hash, entry); + } +} + #[derive(Clone, Debug)] struct EvalEntry { - depth: usize, eval: i32, best_move: T::Move, } -struct Minimax; +struct MNKMinimax; -impl Minimax { +impl MNKMinimax { const WIN_VAL: i32 = i32::MAX; const LOSE_VAL: i32 = i32::MIN; - fn evaluate(&self, game: &G, depth: usize) -> i32 { + fn evaluate( + &self, + game: &MNKBoard, + _depth: i32, + ) -> i32 { match game.winner() { - Some(GameResult::PlayerWon(Player::Player1)) => Minimax::WIN_VAL, - Some(GameResult::PlayerWon(Player::Player2)) => Minimax::LOSE_VAL, + Some(GameResult::PlayerWon(Player::Player1)) => MNKMinimax::WIN_VAL, + Some(GameResult::PlayerWon(Player::Player2)) => MNKMinimax::LOSE_VAL, _ => 0, } } - fn alphabeta( + fn alphabeta( &self, - game: &mut G, - depth: usize, + game: &mut MNKBoard, + depth: i32, mut alpha: i32, mut beta: i32, player: Player, ) -> i32 { + if let Some(entry) = game.transposition.get_entry(&game.hash) { + return entry.eval; + } + if game.winner().is_some() { return self.evaluate(game, depth); } match player { - // maximising player + // maximising player; Player Player::Player1 => { + let mut best_pos_hash: as Game>::Hash = 0; + let mut best_move: Option< as Game>::Move> = None; let mut eval = i32::MIN; + let mut cutoff = false; for m in game.moves() { - game.execute_move(m.clone()).unwrap(); + game.execute_move(m).unwrap(); eval = cmp::max( eval, self.alphabeta(game, depth + 1, alpha, beta, Player::Player2), ); - game.undo_move(m).unwrap(); if eval >= beta { + cutoff = true; + } + if eval > alpha { + alpha = eval; + best_move = Some(m); + best_pos_hash = game.hash; + } + game.undo_move(m).unwrap(); + if cutoff { break; } - alpha = cmp::max(alpha, eval); + } + + if !cutoff { + if let Some(m) = best_move { + let entry = EvalEntry { eval, best_move: m }; + game.transposition.update_eval(best_pos_hash, entry); + } } eval } - // minimising player + // minimising player; AI Player::Player2 => { + let mut best_pos_hash: as Game>::Hash = 0; + let mut best_move: Option< as Game>::Move> = None; let mut eval = i32::MAX; + let mut cutoff = false; for m in game.moves() { - game.execute_move(m.clone()).unwrap(); + game.execute_move(m).unwrap(); eval = cmp::min( eval, self.alphabeta(game, depth + 1, alpha, beta, Player::Player1), ); - game.undo_move(m.clone()).unwrap(); if eval <= alpha { + cutoff = true; + } + if beta < eval { + beta = eval; + best_move = Some(m); + best_pos_hash = game.hash; + } + game.undo_move(m).unwrap(); + if cutoff { break; } - beta = cmp::min(beta, eval); + } + + if !cutoff { + if let Some(m) = best_move { + let entry = EvalEntry { eval, best_move: m }; + game.transposition.update_eval(best_pos_hash, entry); + } } eval } } } - fn next_move(&mut self, game: &mut G) -> G::Move { + fn next_move( + &mut self, + game: &mut MNKBoard, + ) -> as Game>::Move { let move_evals: Vec<_> = game .moves() .iter() .map(|m| { - game.execute_move(m.clone()).unwrap(); + game.execute_move(*m).unwrap(); let eval = self.alphabeta(game, 0, i32::MIN, i32::MAX, Player::Player1); - game.undo_move(m.clone()).unwrap(); - (m.clone(), eval) + game.undo_move(*m).unwrap(); + (*m, eval) }) .collect(); @@ -479,7 +562,7 @@ impl Minimax { .iter() .min_by(|(_, v1), (_, v2)| v1.cmp(v2)) .unwrap(); - best_move.clone() + *best_move } } @@ -492,15 +575,20 @@ fn parse_input(s: &str) -> Result<(usize, usize), Box> { } fn main() { - let mut b: MNKBoard<4, 4, 4> = MNKBoard::new(); - let mut ai = Minimax; + let mut b: MNKBoard<4, 4, 3> = MNKBoard::new(); + let mut ai = MNKMinimax; let mut player = Player::Player1; + println!("{}", b); while b.winner().is_none() { match player { Player::Player1 => { assert_eq!(b.current_player, Player::Player1); let mut line = String::new(); + + print!("Make your move (row, column): "); + std::io::stdout().flush().unwrap(); + std::io::stdin().read_line(&mut line).unwrap(); if let Ok(pos) = parse_input(line.as_str()) { if let Err(msg) = b.execute_move(pos) { @@ -517,6 +605,7 @@ fn main() { assert_eq!(b.current_player, Player::Player2); let ai_move = ai.next_move(&mut b); b.execute_move(ai_move).unwrap(); + println!("The AI has made the move {:?}!", ai_move); println!("{}", b); } } @@ -583,7 +672,7 @@ mod test { let board = MNKBoard::<3, 3, 3>::load_board(board, Player1); - assert_eq!(board.winner(), Some(PlayerWon(Player2))); + assert_eq!(board.winner(), None); let board = [ [None, Some(Player2), None], @@ -673,6 +762,15 @@ mod test { let board = MNKBoard::<3, 3, 2>::load_board(board, Player1); assert_eq!(board.winner(), Some(PlayerWon(Player1))); + + let board = [ + [Some(Player1), Some(Player2), Some(Player1)], + [Some(Player2), Some(Player2), Some(Player1)], + [Some(Player2), Some(Player1), Some(Player1)], + ]; + + let board = MNKBoard::<3, 3, 3>::load_board(board, Player2); + assert_eq!(board.winner(), Some(PlayerWon(Player1))); } #[test] @@ -709,6 +807,26 @@ mod test { board.move_sequence = vec![(0, 2)]; assert_eq!(board.winner(), Some(PlayerWon(Player1))); + + let board = [ + [Some(Player1), Some(Player2), Some(Player1)], + [Some(Player2), Some(Player2), Some(Player1)], + [Some(Player2), Some(Player1), Some(Player1)], + ]; + + let mut board = MNKBoard::<3, 3, 3>::load_board(board, Player2); + board.move_sequence = vec![ + (0, 0), + (1, 1), + (2, 2), + (0, 1), + (2, 1), + (2, 0), + (0, 2), + (1, 0), + (1, 2), + ]; + assert_eq!(board.winner(), Some(PlayerWon(Player1))); } #[test] @@ -743,4 +861,64 @@ mod test { assert_eq!(board.winner(), Some(Draw)); } + + #[test] + fn undo_arbitrary_move() { + let mut board = MNKBoard::<3, 3, 3>::new(); + assert_eq!(board.current_player, Player1); + board.execute_move((0, 1)).unwrap(); + + assert_eq!(board.current_player, Player2); + board.execute_move((0, 0)).unwrap(); + + assert_eq!(board.current_player, Player1); + board.execute_move((1, 1)).unwrap(); + + assert_eq!(board.current_player, Player2); + board.execute_move((2, 1)).unwrap(); + + assert_eq!(board.current_player, Player1); + board.undo_move((2, 1)).unwrap(); + + assert_eq!(board.current_player, Player2); + board.undo_move((0, 0)).unwrap(); + + assert_eq!(board.current_player, Player2); + board.undo_move((0, 1)).unwrap(); + + assert_eq!(board.current_player, Player1); + } + + #[test] + fn zobrist_hash_is_valid() { + // Create Zobrist hash table with following predetermined values: + let mut board = MNKBoard::<3, 3, 3>::new(); + + let white = [[0, 1, 2], [3, 4, 5], [6, 7, 8]]; + let black = [[9, 10, 11], [12, 13, 14], [15, 16, 17]]; + let zobrist_table = [white, black]; + board.transposition.hash_func.hash_values = zobrist_table; + + assert_eq!(board.current_player, Player1); + + board.execute_move((2, 2)).unwrap(); + assert_eq!(board.current_player, Player2); + assert_eq!(board.hash, 8); + + board.execute_move((2, 1)).unwrap(); + assert_eq!(board.current_player, Player1); + assert_eq!(board.hash, 24); + + board.execute_move((2, 0)).unwrap(); + assert_eq!(board.current_player, Player2); + assert_eq!(board.hash, 30); + + board.execute_move((1, 2)).unwrap(); + assert_eq!(board.current_player, Player1); + assert_eq!(board.hash, 16); + + board.undo_move((2, 2)).unwrap(); + assert_eq!(board.current_player, Player1); + assert_eq!(board.hash, 0); + } }