diff --git a/lut.py b/lut.py new file mode 100644 index 0000000..7ae3887 --- /dev/null +++ b/lut.py @@ -0,0 +1,91 @@ +s = set() + +for a in range(7): + for b in range(7): + for c in range(7): + s.add(tuple(sorted((a, b, c)))) + +mapping = sorted(s) + +n = 128 +filler = 0 + +print(f"const MAPPING: [(u8, u8, u8); {len(mapping)}] = {list(mapping)};") +print() + +################################################################################ + + +def score(column): + values = {die: 0 for die in range(1, 7)} + for die in column: + if die == 0: + continue + values[die] += 1 + + multiplier = {0: 1, 1: 1, 2: 2, 3: 9} + + return sum(value * count * multiplier[count] for (value, count) in values.items()) + + +score_lut = [filler for _ in range(n)] +for i, column in enumerate(mapping): + score_lut[i] = score(column) + +print(f"const SCORE_LUT: [u8; {n}] = {score_lut};") +print() + +################################################################################ + + +def add(die, column): + if 0 not in column: + raise ValueError("Invalid Move") + + new_column = list(column) + new_column[0] = die # sorted and a zero is present -> first index is 0 + + return tuple(sorted(new_column)) + + +add_lut = {die: [filler for _ in range(32)] for die in range(1, 7)} +for die in range(1, 7): + for i, column in enumerate(mapping): + try: + new_column = add(die, column) + except ValueError: + continue + add_lut[die][i] = mapping.index(new_column) + +print(f"const ADD_LUT: [[u8; 32]; 8] = [") +print(f" [{filler}; 32],") +for die in range(1, 7): + print(f" {add_lut[die]},") +print(f" [{filler}; 32],") +print("];"); +print() + +################################################################################ + + +def remove(die, opposite): + new_opposite = list(opposite) + for i in range(3): + if new_opposite[i] == die: + new_opposite[i] = 0 + return tuple(sorted(new_opposite)) + + +remove_lut = {die: [filler for _ in range(n)] for die in range(1, 7)} +for die in range(1, 7): + for i, column in enumerate(mapping): + new_column = remove(die, column) + remove_lut[die][i] = mapping.index(new_column) + +print(f"const REMOVE_LUT: [[u8; {n}]; 8] = [") +print(f" [{filler}; {n}],") +for die in range(1, 7): + print(f" {remove_lut[die]},") +print(f" [{filler}; {n}],") +print("];"); +print() diff --git a/src/game.rs b/src/game.rs index 9509a02..cfe54db 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,152 +1,166 @@ +const MAPPING: [(u8, u8, u8); 84] = [(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 0, 3), (0, 0, 4), (0, 0, 5), (0, 0, 6), (0, 1, 1), (0, 1, 2), (0, 1, 3), (0, 1, 4), (0, 1, 5), (0, 1, 6), (0, 2, 2), (0, 2, 3), (0, 2, 4), (0, 2, 5), (0, 2, 6), (0, 3, 3), (0, 3, 4), (0, 3, 5), (0, 3, 6), (0, 4, 4), (0, 4, 5), (0, 4, 6), (0, 5, 5), (0, 5, 6), (0, 6, 6), (1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 1, 4), (1, 1, 5), (1, 1, 6), (1, 2, 2), (1, 2, 3), (1, 2, 4), (1, 2, 5), (1, 2, 6), (1, 3, 3), (1, 3, 4), (1, 3, 5), (1, 3, 6), (1, 4, 4), (1, 4, 5), (1, 4, 6), (1, 5, 5), (1, 5, 6), (1, 6, 6), (2, 2, 2), (2, 2, 3), (2, 2, 4), (2, 2, 5), (2, 2, 6), (2, 3, 3), (2, 3, 4), (2, 3, 5), (2, 3, 6), (2, 4, 4), (2, 4, 5), (2, 4, 6), (2, 5, 5), (2, 5, 6), (2, 6, 6), (3, 3, 3), (3, 3, 4), (3, 3, 5), (3, 3, 6), (3, 4, 4), (3, 4, 5), (3, 4, 6), (3, 5, 5), (3, 5, 6), (3, 6, 6), (4, 4, 4), (4, 4, 5), (4, 4, 6), (4, 5, 5), (4, 5, 6), (4, 6, 6), (5, 5, 5), (5, 5, 6), (5, 6, 6), (6, 6, 6)]; + +const SCORE_LUT: [u8; 128] = [ + 0, 1, 2, 3, 4, 5, 6, 4, 3, 4, 5, 6, 7, 8, 5, 6, 7, 8, 12, 7, 8, 9, 16, 9, 10, 20, 11, 24, 27, + 6, 7, 8, 9, 10, 9, 6, 7, 8, 9, 13, 8, 9, 10, 17, 10, 11, 21, 12, 25, 54, 11, 12, 13, 14, 14, 9, + 10, 11, 18, 11, 12, 22, 13, 26, 81, 16, 17, 18, 19, 12, 13, 23, 14, 27, 108, 21, 22, 24, 15, + 28, 135, 26, 29, 162, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; + +const ADD_LUT: [[u8; 32]; 8] = [ + [0; 32], + [ + 1, 7, 8, 9, 10, 11, 12, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 0, 0, 0, 0, + ], + [ + 2, 8, 13, 14, 15, 16, 17, 29, 34, 35, 36, 37, 38, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, 62, 63, 0, 0, 0, 0, + ], + [ + 3, 9, 14, 18, 19, 20, 21, 30, 35, 39, 40, 41, 42, 50, 54, 55, 56, 57, 64, 65, 66, 67, 68, + 69, 70, 71, 72, 73, 0, 0, 0, 0, + ], + [ + 4, 10, 15, 19, 22, 23, 24, 31, 36, 40, 43, 44, 45, 51, 55, 58, 59, 60, 65, 68, 69, 70, 74, + 75, 76, 77, 78, 79, 0, 0, 0, 0, + ], + [ + 5, 11, 16, 20, 23, 25, 26, 32, 37, 41, 44, 46, 47, 52, 56, 59, 61, 62, 66, 69, 71, 72, 75, + 77, 78, 80, 81, 82, 0, 0, 0, 0, + ], + [ + 6, 12, 17, 21, 24, 26, 27, 33, 38, 42, 45, 47, 48, 53, 57, 60, 62, 63, 67, 70, 72, 73, 76, + 78, 79, 81, 82, 83, 0, 0, 0, 0, + ], + [0; 32], +]; + +const REMOVE_LUT: [[u8; 128]; 8] = [ + [0; 128], + [ + 0, 0, 2, 3, 4, 5, 6, 0, 2, 3, 4, 5, 6, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 0, 2, 3, 4, 5, 6, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, + 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], + [ + 0, 1, 0, 3, 4, 5, 6, 7, 1, 9, 10, 11, 12, 0, 3, 4, 5, 6, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 7, 30, 31, 32, 33, 1, 9, 10, 11, 12, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 0, + 3, 4, 5, 6, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], + [ + 0, 1, 2, 0, 4, 5, 6, 7, 8, 1, 10, 11, 12, 13, 2, 15, 16, 17, 0, 4, 5, 6, 22, 23, 24, 25, + 26, 27, 28, 29, 7, 31, 32, 33, 34, 8, 36, 37, 38, 1, 10, 11, 12, 43, 44, 45, 46, 47, 48, + 49, 13, 51, 52, 53, 2, 15, 16, 17, 58, 59, 60, 61, 62, 63, 0, 4, 5, 6, 22, 23, 24, 25, 26, + 27, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], + [ + 0, 1, 2, 3, 0, 5, 6, 7, 8, 9, 1, 11, 12, 13, 14, 2, 16, 17, 18, 3, 20, 21, 0, 5, 6, 25, 26, + 27, 28, 29, 30, 7, 32, 33, 34, 35, 8, 37, 38, 39, 9, 41, 42, 1, 11, 12, 46, 47, 48, 49, 50, + 13, 52, 53, 54, 14, 56, 57, 2, 16, 17, 61, 62, 63, 64, 18, 66, 67, 3, 20, 21, 71, 72, 73, + 0, 5, 6, 25, 26, 27, 80, 81, 82, 83, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], + [ + 0, 1, 2, 3, 4, 0, 6, 7, 8, 9, 10, 1, 12, 13, 14, 15, 2, 17, 18, 19, 3, 21, 22, 4, 24, 0, 6, + 27, 28, 29, 30, 31, 7, 33, 34, 35, 36, 8, 38, 39, 40, 9, 42, 43, 10, 45, 1, 12, 48, 49, 50, + 51, 13, 53, 54, 55, 14, 57, 58, 15, 60, 2, 17, 63, 64, 65, 18, 67, 68, 19, 70, 3, 21, 73, + 74, 22, 76, 4, 24, 79, 0, 6, 27, 83, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], + [ + 0, 1, 2, 3, 4, 5, 0, 7, 8, 9, 10, 11, 1, 13, 14, 15, 16, 2, 18, 19, 20, 3, 22, 23, 4, 25, + 5, 0, 28, 29, 30, 31, 32, 7, 34, 35, 36, 37, 8, 39, 40, 41, 9, 43, 44, 10, 46, 11, 1, 49, + 50, 51, 52, 13, 54, 55, 56, 14, 58, 59, 15, 61, 16, 2, 64, 65, 66, 18, 68, 69, 19, 71, 20, + 3, 74, 75, 22, 77, 23, 4, 80, 25, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], + [0; 128], +]; + +pub fn add_is_valid(column: u8) -> bool { + column < 28 +} + +pub fn add(die: u8, column: u8) -> u8 { + ADD_LUT[(die & 0b111) as usize][(column & 0b11111) as usize] +} + +pub fn remove(die: u8, column: u8) -> u8 { + REMOVE_LUT[(die & 0b111) as usize][(column & 0b1111111) as usize] +} + +#[derive(Debug, Clone, Copy)] +pub enum Column { + L = 0, + C = 2, + R = 4, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Player { Bottom, Top, } -#[derive(Debug, Clone, Copy)] -pub enum Column { - Left, - Center, - Right, -} - -/// Encodes the state of the game -/// -/// Each cell can take 7 values (empty or 1-6) -> 3 * 18 = 54 bits required. -/// We need one additional bit to store which player's turn it is. -/// -/// Cell Layout: -/// ``` -/// 11 14 17 -/// 10 13 16 -/// 9 12 15 -/// -/// 0 3 6 -/// 1 4 7 -/// 2 5 8 -/// ``` -/// -/// Offsets: -/// ``` -/// 33 42 51 -/// 30 39 48 -/// 27 36 45 -/// -/// 0 9 18 -/// 3 12 21 -/// 6 15 24 -/// ``` -/// -/// current_player: `54` -#[derive(Debug, Clone)] -pub struct Board(u64); - #[derive(Debug)] pub struct IllegalMove(()); +pub struct Board { + columns: [u8; 6], + pub current_player: Player, +} + impl Board { pub fn empty() -> Self { - Self::from_values([&[], &[], &[]], [&[], &[], &[]], Player::Bottom) - } - - pub fn from_values(bottom: [&[u8]; 3], top: [&[u8]; 3], current_player: Player) -> Self { - let mut bits = 0; - for (start, half) in [(0, bottom), (27, top)] { - for (index, &col) in half.iter().enumerate() { - for (offset, &val) in col.iter().enumerate() { - let shift = start + index * 9 + offset * 3; - bits |= (val as u64) << shift; - } - } + Self { + columns: [0, 0, 0, 0, 0, 0], + current_player: Player::Bottom, } - - if current_player == Player::Top { - bits |= 1 << 54 - } - - Self(bits) } pub fn visualize(&self) { - let &Self(bits) = self; - - let print_half = |rows| { - for row in rows { - for offset in row { - let value = (bits >> offset & 0b111u64) as u8; - if value > 0 { - print!("{value} "); - } else { - print!("_ "); - } - } - println!() - } - }; - - print_half([[33, 42, 51], [30, 39, 48], [27, 36, 45]]); - println!(); - print_half([[0, 9, 18], [3, 12, 21], [6, 15, 24]]); - } - - pub fn current_player(&self) -> Player { - let &Self(bits) = self; - if bits >> 54 & 1 == 0 { - Player::Bottom - } else { - Player::Top - } - } - - pub fn is_game_over(&self) -> bool { - let &Self(bits) = self; - let has_empty_slots = |offset| -> bool { - for i in [0, 3, 6] { - if (bits >> offset + i) & 0b111u64 == 0u64 { - return true; - } - } - false - }; + let [a, b, c, d, e, f] = self.columns; - !((has_empty_slots(0) || has_empty_slots(9) || has_empty_slots(18)) - && (has_empty_slots(27) || has_empty_slots(36) || has_empty_slots(45))) + println!("{b:02X} {d:02X} {f:02X}"); + println!("{a:02X} {c:02X} {e:02X}"); + + let (l1, l2, l3) = MAPPING[b as usize]; + let (l4, l5, l6) = MAPPING[a as usize]; + let (c1, c2, c3) = MAPPING[d as usize]; + let (c4, c5, c6) = MAPPING[c as usize]; + let (r1, r2, r3) = MAPPING[f as usize]; + let (r4, r5, r6) = MAPPING[e as usize]; + + println!("{l1} {c1} {r1}"); + println!("{l2} {c2} {r2}"); + println!("{l3} {c3} {r3}"); + println!(); + println!("{l6} {c6} {r6}"); + println!("{l5} {c5} {r5}"); + println!("{l4} {c4} {r4}"); } - /// (bottom, top) pub fn column_scores(&self) -> ((u8, u8, u8), (u8, u8, u8)) { - let &Self(bits) = self; - - let score_col = |offset| { - let mut counts = [0; 6]; - for i in [0, 3, 6] { - let value = ((bits >> offset + i) & 0b111u64) as usize; - if value == 0 { - continue; - } - counts[value - 1] += 1; - } - - counts - .iter() - .enumerate() - .map(|(value, &count)| { - let value = (value + 1) as u8; - value - * count - * match count { - 2 => 2, - 3 => 9, - _ => 1, - } - }) - .sum() - }; + let [a, b, c, d, e, f] = self.columns; ( - (score_col(0), score_col(9), score_col(18)), - (score_col(27), score_col(36), score_col(45)), + ( + SCORE_LUT[(a & 0x7F) as usize], + SCORE_LUT[(b & 0x7F) as usize], + SCORE_LUT[(c & 0x7F) as usize], + ), + ( + SCORE_LUT[(d & 0x7F) as usize], + SCORE_LUT[(e & 0x7F) as usize], + SCORE_LUT[(f & 0x7F) as usize], + ), ) } @@ -155,83 +169,37 @@ impl Board { a as i16 + b as i16 + c as i16 - d as i16 - e as i16 - f as i16 } - pub fn play(&self, column: Column, value: u8) -> Result { - let Self(mut bits) = *self; - - let mut apply_move = |offset, opposite| -> Result<(), IllegalMove> { - // push value to the first zero after offset - let mut found = false; - for index in [0, 3, 6] { - if ((bits >> offset + index) & 0b111u64) != 0u64 { - continue; - } - - found = true; - bits &= !((0b111u64 << offset + index) as u64); - bits |= (value as u64) << offset + index; - - break; - } - - // raise if all three are non-zero - if !found { - return Err(IllegalMove(())); - } - - // eliminate all occurences from value after opposite - let mut tmp = [0, 0, 0]; - let mut i = 0; - for index in [0, 3, 6] { - let other = ((bits >> opposite + index) & 0b111u64) as u8; - if other == value { - continue; - } - tmp[i] = other; - i += 1; - } - - if i != 3 { - for (index, &value) in tmp.iter().enumerate() { - bits &= !((0b111u64 << opposite + index) as u64); - bits |= (value as u64) << opposite + index; - } - } - - Ok(()) - }; - - match (column, self.current_player()) { - (Column::Left, Player::Bottom) => apply_move(0, 27)?, - (Column::Left, Player::Top) => apply_move(27, 0)?, - (Column::Center, Player::Bottom) => apply_move(9, 36)?, - (Column::Center, Player::Top) => apply_move(36, 9)?, - (Column::Right, Player::Bottom) => apply_move(18, 45)?, - (Column::Right, Player::Top) => apply_move(45, 18)?, + pub fn play(&self, column: Column, die: u8) -> Result { + let a = &self.columns; + if !add_is_valid(a[column as usize]) { + return Err(IllegalMove(())); } - // switch current player - bits ^= 1 << 54; + let mut a = a.clone(); - Ok(Self(bits)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_example() { - let board = Board::from_values( - [&[6, 5, 5], &[5, 5], &[1, 5]], - [&[2, 2, 1], &[4, 3, 3], &[4, 2, 3]], - Player::Bottom, - ); - - board.visualize(); - - assert_eq!(board.column_scores(), ((26, 20, 6), (9, 16, 9))); - assert!(board.score() > 0); - assert!(board.is_game_over()); + let (current, opposite) = match self.current_player { + Player::Bottom => (column as usize, column as usize + 1), + Player::Top => (column as usize + 1, column as usize) + }; + + a[current] = add(die, a[current]); + a[opposite] = remove(die, a[opposite]); + + let player = match self.current_player { + Player::Top => Player::Bottom, + Player::Bottom => Player::Top, + }; + + Ok(Self { + columns: a, + current_player: player, + }) + } + + pub fn is_game_over(&self) -> bool { + let [a, b, c, d, e, f] = self.columns; + + !((add_is_valid(a) | add_is_valid(b) | add_is_valid(c)) + & (add_is_valid(d) | add_is_valid(e) | add_is_valid(f))) } } diff --git a/src/main.rs b/src/main.rs index 60c4fd7..f48fb31 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,11 +9,11 @@ fn main() { while !b.is_game_over() { b.visualize(); - println!("Current player: {:?}", b.current_player()); + println!("Current player: {:?}", b.current_player); let d6 = (Select::new().items(&[1, 2, 3, 4, 5, 6]).interact().unwrap() + 1) as u8; - let column = [Column::Left, Column::Center, Column::Right][Select::new() + let column = [Column::L, Column::C, Column::R][Select::new() .items(&["Left", "Center", "Right"]) .interact() .unwrap()];