|
|
@ -1,9 +1,42 @@
|
|
|
|
|
|
|
|
use crate::board::Board;
|
|
|
|
use crate::expr::is_valid_guess_of_tokens;
|
|
|
|
use crate::expr::is_valid_guess_of_tokens;
|
|
|
|
use crate::game::Hand;
|
|
|
|
use crate::game::Hand;
|
|
|
|
use crate::lexer::{lexer_reuse, Token};
|
|
|
|
use crate::lexer::{lexer_reuse, Token};
|
|
|
|
|
|
|
|
use crate::position::{Direction, Grid2d};
|
|
|
|
use crate::tile::{Digit, Operator, Tile};
|
|
|
|
use crate::tile::{Digit, Operator, Tile};
|
|
|
|
use itertools::Itertools;
|
|
|
|
use itertools::Itertools;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Configuration for the combination generator.
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
|
|
|
pub struct CombinationConfig {
|
|
|
|
|
|
|
|
/// The minimum number of digits to use in the expression.
|
|
|
|
|
|
|
|
min_digits: usize,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// The maximum number of digits to use in the expression.
|
|
|
|
|
|
|
|
max_digits: Option<usize>,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl Default for CombinationConfig {
|
|
|
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
|
|
|
CombinationConfig {
|
|
|
|
|
|
|
|
min_digits: 2,
|
|
|
|
|
|
|
|
max_digits: None,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Context for the combination generator.
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// To limit the number of combinations generated, the generator can be configured
|
|
|
|
|
|
|
|
/// to only generate combinations that are valid in the current context.
|
|
|
|
|
|
|
|
pub trait GenerationContext {
|
|
|
|
|
|
|
|
/// The tiles that are available to the player.
|
|
|
|
|
|
|
|
fn tiles(&self) -> &Hand;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Verify that a move is playable, i.e. that the expression can be placed on the board.
|
|
|
|
|
|
|
|
fn is_playable(&self, tile: &[Tile]) -> bool;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn merge_expression(
|
|
|
|
fn merge_expression(
|
|
|
|
numbers: &[&Digit],
|
|
|
|
numbers: &[&Digit],
|
|
|
|
operators: &[&Operator],
|
|
|
|
operators: &[&Operator],
|
|
|
@ -21,7 +54,21 @@ fn merge_expression(
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn generate_valid_combinations(hand: &Hand) -> Vec<Vec<Tile>> {
|
|
|
|
/// Generate all possible valid combinations of tiles in a hand.
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// To further limit the number of combinations generated, configuration options
|
|
|
|
|
|
|
|
/// can directly be passed to the [`generate_valid_combinations`] function.
|
|
|
|
|
|
|
|
pub fn generate_valid_all_combinations(hand: &Hand) -> Vec<Vec<Tile>> {
|
|
|
|
|
|
|
|
generate_valid_combinations(
|
|
|
|
|
|
|
|
CombinationConfig::default(),
|
|
|
|
|
|
|
|
SimpleGenerationContext::new(hand),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn generate_valid_combinations(
|
|
|
|
|
|
|
|
config: CombinationConfig,
|
|
|
|
|
|
|
|
context: impl GenerationContext,
|
|
|
|
|
|
|
|
) -> Vec<Vec<Tile>> {
|
|
|
|
let mut combinations = Vec::new();
|
|
|
|
let mut combinations = Vec::new();
|
|
|
|
|
|
|
|
|
|
|
|
// Separate numbers and operators
|
|
|
|
// Separate numbers and operators
|
|
|
@ -38,8 +85,14 @@ pub fn generate_valid_combinations(hand: &Hand) -> Vec<Vec<Tile>> {
|
|
|
|
let mut trial: Vec<Tile> = Vec::with_capacity(numbers.len() + operators.len() + 1);
|
|
|
|
let mut trial: Vec<Tile> = Vec::with_capacity(numbers.len() + operators.len() + 1);
|
|
|
|
let mut tokens: Vec<Token> = Vec::with_capacity(numbers.len() + operators.len() + 1);
|
|
|
|
let mut tokens: Vec<Token> = Vec::with_capacity(numbers.len() + operators.len() + 1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let digits_range = {
|
|
|
|
|
|
|
|
let min_digits = config.min_digits;
|
|
|
|
|
|
|
|
let max_digits = config.max_digits.unwrap_or(numbers.len());
|
|
|
|
|
|
|
|
min_digits..=max_digits
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Generate all possible permutations, with an increasing number of tiles
|
|
|
|
// Generate all possible permutations, with an increasing number of tiles
|
|
|
|
for nb_digits in 2..=numbers.len() {
|
|
|
|
for nb_digits in digits_range {
|
|
|
|
for digits in numbers.iter().permutations(nb_digits) {
|
|
|
|
for digits in numbers.iter().permutations(nb_digits) {
|
|
|
|
// Then try to place the equals sign at each possible position
|
|
|
|
// Then try to place the equals sign at each possible position
|
|
|
|
// Since equality is commutative, we only need to try half of the positions
|
|
|
|
// Since equality is commutative, we only need to try half of the positions
|
|
|
@ -48,7 +101,7 @@ pub fn generate_valid_combinations(hand: &Hand) -> Vec<Vec<Tile>> {
|
|
|
|
for operators in operators.iter().permutations(nb_operators) {
|
|
|
|
for operators in operators.iter().permutations(nb_operators) {
|
|
|
|
merge_expression(&digits, &operators, equals_idx, &mut trial);
|
|
|
|
merge_expression(&digits, &operators, equals_idx, &mut trial);
|
|
|
|
lexer_reuse(&trial, &mut tokens);
|
|
|
|
lexer_reuse(&trial, &mut tokens);
|
|
|
|
if is_valid_guess_of_tokens(&tokens) {
|
|
|
|
if is_valid_guess_of_tokens(&tokens) && context.is_playable(&trial) {
|
|
|
|
combinations.push(trial.clone());
|
|
|
|
combinations.push(trial.clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
trial.clear();
|
|
|
|
trial.clear();
|
|
|
@ -61,6 +114,58 @@ pub fn generate_valid_combinations(hand: &Hand) -> Vec<Vec<Tile>> {
|
|
|
|
combinations
|
|
|
|
combinations
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// A convenient implementation of [`GenerationContext`] that can be used when
|
|
|
|
|
|
|
|
/// the playability is not relevant.
|
|
|
|
|
|
|
|
struct SimpleGenerationContext<'a> {
|
|
|
|
|
|
|
|
tiles: &'a Hand,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl<'a> SimpleGenerationContext<'a> {
|
|
|
|
|
|
|
|
fn new(tiles: &'a Hand) -> Self {
|
|
|
|
|
|
|
|
SimpleGenerationContext { tiles }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl<'a> GenerationContext for SimpleGenerationContext<'a> {
|
|
|
|
|
|
|
|
fn tiles(&self) -> &Hand {
|
|
|
|
|
|
|
|
self.tiles
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn is_playable(&self, _tile: &[Tile]) -> bool {
|
|
|
|
|
|
|
|
true
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// A [`GenerationContext`] that can be used to check if a move is playable on
|
|
|
|
|
|
|
|
/// a board.
|
|
|
|
|
|
|
|
pub struct BoardGenerationContext<'a> {
|
|
|
|
|
|
|
|
tiles: &'a Hand,
|
|
|
|
|
|
|
|
board: &'a Board,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl<'a> BoardGenerationContext<'a> {
|
|
|
|
|
|
|
|
fn new(tiles: &'a Hand, board: &'a Board) -> Self {
|
|
|
|
|
|
|
|
BoardGenerationContext { tiles, board }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl<'a> GenerationContext for BoardGenerationContext<'a> {
|
|
|
|
|
|
|
|
fn tiles(&self) -> &Hand {
|
|
|
|
|
|
|
|
self.tiles
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn is_playable(&self, tile: &[Tile]) -> bool {
|
|
|
|
|
|
|
|
for pos in self.board.row_iter() {
|
|
|
|
|
|
|
|
for &direction in &[Direction::Down, Direction::Right] {
|
|
|
|
|
|
|
|
if self.board.is_playable(tile, pos, direction) {
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
false
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use super::*;
|
|
|
@ -75,7 +180,34 @@ mod tests {
|
|
|
|
Tile::Operator(Operator::Add),
|
|
|
|
Tile::Operator(Operator::Add),
|
|
|
|
Tile::Operator(Operator::Subtract),
|
|
|
|
Tile::Operator(Operator::Subtract),
|
|
|
|
]);
|
|
|
|
]);
|
|
|
|
let combinations = generate_valid_combinations(&hand);
|
|
|
|
let combinations = generate_valid_all_combinations(&hand);
|
|
|
|
assert_eq!(combinations.len(), 4);
|
|
|
|
assert_eq!(combinations.len(), 4);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn generate_combinations_with_board() {
|
|
|
|
|
|
|
|
let hand = Hand::new(vec![
|
|
|
|
|
|
|
|
Tile::Digit(Digit::new(-5)),
|
|
|
|
|
|
|
|
Tile::Digit(Digit::new(-5)),
|
|
|
|
|
|
|
|
Tile::Digit(Digit::new(1)),
|
|
|
|
|
|
|
|
Tile::Digit(Digit::new(-6)),
|
|
|
|
|
|
|
|
Tile::Operator(Operator::Add),
|
|
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
let board = Board::new(3, 3);
|
|
|
|
|
|
|
|
board.set_tile(1, 0, Tile::Digit(Digit::new(9)));
|
|
|
|
|
|
|
|
board.set_tile(1, 1, Tile::Equals);
|
|
|
|
|
|
|
|
board.set_tile(1, 2, Tile::Digit(Digit::new(9)));
|
|
|
|
|
|
|
|
let combinations = generate_valid_combinations(
|
|
|
|
|
|
|
|
CombinationConfig::default(),
|
|
|
|
|
|
|
|
BoardGenerationContext::new(&hand, &board),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
|
|
combinations,
|
|
|
|
|
|
|
|
vec![vec![
|
|
|
|
|
|
|
|
Tile::Digit(Digit::new(-5)),
|
|
|
|
|
|
|
|
Tile::Equals,
|
|
|
|
|
|
|
|
Tile::Digit(Digit::new(-5)),
|
|
|
|
|
|
|
|
]]
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|