Draft generation of valid AI movements
This commit is contained in:
@@ -16,7 +16,7 @@ steps:
|
|||||||
- name: test
|
- name: test
|
||||||
image: rust:1.67
|
image: rust:1.67
|
||||||
commands:
|
commands:
|
||||||
- cargo test
|
- cargo test --all-features
|
||||||
volumes:
|
volumes:
|
||||||
- name: cargo-cache
|
- name: cargo-cache
|
||||||
path: /cache/cargo
|
path: /cache/cargo
|
||||||
|
@@ -7,4 +7,8 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
enum-map = "2.4.2"
|
enum-map = "2.4.2"
|
||||||
|
itertools = { version = "0.10.5", optional = true }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
ai = ["itertools"]
|
||||||
|
81
board-shared/src/ai.rs
Normal file
81
board-shared/src/ai.rs
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
use crate::expr::are_valid_expressions;
|
||||||
|
use crate::game::Hand;
|
||||||
|
use crate::lexer::lexer;
|
||||||
|
use crate::parser::parse;
|
||||||
|
use crate::tile::{Digit, Operator, Tile};
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
fn merge_expression(
|
||||||
|
numbers: &[&Digit],
|
||||||
|
operators: &[&Operator],
|
||||||
|
equals_idx: usize,
|
||||||
|
buf: &mut Vec<Tile>,
|
||||||
|
) {
|
||||||
|
let mut op_it = operators.iter();
|
||||||
|
for (i, &number) in numbers.iter().enumerate() {
|
||||||
|
buf.push(Tile::Digit(*number));
|
||||||
|
if i == equals_idx {
|
||||||
|
buf.push(Tile::Equals);
|
||||||
|
} else if let Some(&&operator) = op_it.next() {
|
||||||
|
buf.push(Tile::Operator(operator));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_valid_combinations(hand: &Hand) -> Vec<Vec<Tile>> {
|
||||||
|
let mut combinations = Vec::new();
|
||||||
|
|
||||||
|
// Separate numbers and operators
|
||||||
|
let mut numbers = Vec::new();
|
||||||
|
let mut operators = Vec::new();
|
||||||
|
for &tile in &hand.tiles {
|
||||||
|
match tile {
|
||||||
|
Tile::Digit(digit) => numbers.push(digit),
|
||||||
|
Tile::Operator(operator) => operators.push(operator),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut trial: Vec<Tile> = Vec::with_capacity(numbers.len() + operators.len() + 1);
|
||||||
|
|
||||||
|
// Generate all possible permutations, with an increasing number of tiles
|
||||||
|
for nb_digits in 2..=numbers.len() {
|
||||||
|
for digits in numbers.iter().permutations(nb_digits) {
|
||||||
|
// Then try to place the equals sign at each possible position
|
||||||
|
// Since equality is commutative, we only need to try half of the positions
|
||||||
|
for equals_idx in 0..(nb_digits / 2) {
|
||||||
|
for operators in operators.iter().permutations(nb_digits - 2) {
|
||||||
|
merge_expression(&digits, &operators, equals_idx, &mut trial);
|
||||||
|
if let Ok(tokens) = lexer(&trial) {
|
||||||
|
if let Ok(expressions) = parse(&tokens) {
|
||||||
|
if are_valid_expressions(&expressions) {
|
||||||
|
combinations.push(trial.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trial.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
combinations
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::tile::{Digit, Operator};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generate_combinations() {
|
||||||
|
let hand = Hand::new(vec![
|
||||||
|
Tile::Digit(Digit::new(1)),
|
||||||
|
Tile::Digit(Digit::new(3)),
|
||||||
|
Tile::Digit(Digit::new(4)),
|
||||||
|
Tile::Operator(Operator::Add),
|
||||||
|
Tile::Operator(Operator::Subtract),
|
||||||
|
]);
|
||||||
|
let combinations = generate_valid_combinations(&hand);
|
||||||
|
assert_eq!(combinations.len(), 4);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,7 +1,7 @@
|
|||||||
use std::error::Error;
|
|
||||||
use crate::tile::Operator;
|
use crate::tile::Operator;
|
||||||
use enum_map::EnumMap;
|
use enum_map::EnumMap;
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
type DeckSize = u16;
|
type DeckSize = u16;
|
||||||
type DigitDeck = [DeckSize; 19];
|
type DigitDeck = [DeckSize; 19];
|
||||||
|
@@ -14,6 +14,10 @@ pub struct Hand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Hand {
|
impl Hand {
|
||||||
|
pub fn new(tiles: Vec<Tile>) -> Self {
|
||||||
|
Self { tiles }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn count_missing_operators(&self) -> usize {
|
pub fn count_missing_operators(&self) -> usize {
|
||||||
4usize.saturating_sub(
|
4usize.saturating_sub(
|
||||||
self.tiles
|
self.tiles
|
||||||
@@ -38,8 +42,9 @@ impl Hand {
|
|||||||
.push(Tile::Operator(deck.rand_operator().ok_or(EmptyDeckError)?));
|
.push(Tile::Operator(deck.rand_operator().ok_or(EmptyDeckError)?));
|
||||||
}
|
}
|
||||||
for _ in 0..self.count_missing_numbers() {
|
for _ in 0..self.count_missing_numbers() {
|
||||||
self.tiles
|
self.tiles.push(Tile::Digit(Digit::new(
|
||||||
.push(Tile::Digit(Digit::new(deck.rand_digit().ok_or(EmptyDeckError)?)));
|
deck.rand_digit().ok_or(EmptyDeckError)?,
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -51,4 +56,8 @@ impl Hand {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = &Tile> {
|
||||||
|
self.tiles.iter()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
#[cfg(feature = "ai")]
|
||||||
|
pub mod ai;
|
||||||
pub mod board;
|
pub mod board;
|
||||||
pub mod deck;
|
pub mod deck;
|
||||||
pub mod expr;
|
pub mod expr;
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
use crate::lexer::Token;
|
use crate::lexer::Token;
|
||||||
use crate::tile::Operator;
|
use crate::tile::Operator;
|
||||||
|
use std::fmt;
|
||||||
use std::iter::Peekable;
|
use std::iter::Peekable;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
@@ -9,6 +10,18 @@ pub enum Expression {
|
|||||||
Binary(Operator, Box<Expression>, Box<Expression>),
|
Binary(Operator, Box<Expression>, Box<Expression>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Expression {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Expression::Digit(value) => write!(f, "{}", value),
|
||||||
|
Expression::Parentheses(expr) => write!(f, "({})", expr),
|
||||||
|
Expression::Binary(operator, left, right) => {
|
||||||
|
write!(f, "{} {} {}", left, operator, right)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type Expressions = Vec<Expression>;
|
pub type Expressions = Vec<Expression>;
|
||||||
|
|
||||||
pub fn parse(tokens: &[Token]) -> Result<Expressions, ()> {
|
pub fn parse(tokens: &[Token]) -> Result<Expressions, ()> {
|
||||||
|
Reference in New Issue
Block a user