Change implementation to use LUT

This commit is contained in:
Kai Vogelgesang 2024-01-01 14:37:35 +01:00
parent 2244651aa1
commit 0767d5d8c1
Signed by: kai
GPG Key ID: 3FC8578CC818A9EB
3 changed files with 264 additions and 205 deletions

91
lut.py Normal file
View File

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

View File

@ -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<Self, IllegalMove> {
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<Self, IllegalMove> {
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)))
}
}

View File

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