Include a serde feature in the shared crate

This commit is contained in:
2024-03-23 13:36:28 +01:00
parent 3d93bf0fda
commit f1962503b8
22 changed files with 134 additions and 281 deletions

View File

@@ -7,8 +7,10 @@ edition = "2021"
[dependencies]
enum-map = "2.4.2"
itertools = { version = "0.10.5", optional = true }
itertools = { version = "0.12.1", optional = true }
rand = "0.8.5"
serde = { version = "1.0.197", features = ["derive"], optional = true }
[features]
ai = ["itertools"]
ai = ["dep:itertools"]
serde = ["dep:serde"]

View File

@@ -12,6 +12,7 @@ const DEFAULT_BOARD_SIZE: usize = 19;
/// to create a new board with a default size. To create a board with a custom
/// size, use [`Board::new()`].
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Board {
tiles: Vec<Option<Tile>>,
width: usize,
@@ -192,7 +193,7 @@ impl Board {
}
/// Tests whether the given positions are part of the same chain.
fn belong_to_same_chain(&self, positions: &Vec<Position2d>, direction: Direction) -> bool {
fn belong_to_same_chain(&self, positions: &[Position2d], direction: Direction) -> bool {
let mut it = positions.iter().copied().peekable();
while let Some(mut pos) = it.next() {
if let Some(&next) = it.peek() {

View File

@@ -54,11 +54,7 @@ impl Hand {
}
pub fn remove(&mut self, idx: usize) -> Option<Tile> {
if idx < self.tiles.len() {
Some(self.tiles.remove(idx))
} else {
None
}
(idx < self.tiles.len()).then(|| self.tiles.remove(idx))
}
pub fn iter(&self) -> impl Iterator<Item = &Tile> {

View File

@@ -83,7 +83,7 @@ mod tests {
#[test]
fn test_lex() {
let input = vec![
let input = [
Tile::Digit(Digit::new(1)),
Tile::Digit(Digit::new(2)),
Tile::Digit(Digit::new(3)),
@@ -104,7 +104,7 @@ mod tests {
#[test]
fn test_lex_parentheses() {
let input = vec![
let input = [
Tile::Digit(Digit::new(1)),
Tile::Operator(Operator::Subtract),
Tile::Digit(Digit {
@@ -133,7 +133,7 @@ mod tests {
#[test]
fn test_lex_parentheses_long() {
let input = vec![
let input = [
Tile::Digit(Digit {
value: 1,
has_left_parenthesis: true,

View File

@@ -1,15 +1,15 @@
///! # Scrabble with Numbers
///!
///! This crate provides the core game logic for the Scrabble with Numbers game.
///! It can be used standalone, or as a library for other projects.
///!
///! ## Features
///! - Create and verify game expressions, such as `2*3+4*5`.
///! - Generate valid automatic moves for a given board state, with the `ai` feature.
///! - Check if a player move valid.
///! - Calculate the score of an expression.
///!
///! If you are looking for a server implementation, see the `board-server` crate.
//! # Scrabble with Numbers
//!
//! This crate provides the core game logic for the Scrabble with Numbers game.
//! It can be used standalone, or as a library for other projects.
//!
//! ## Features
//! - Create and verify game expressions, such as `2*3+4*5`.
//! - Generate valid automatic moves for a given board state, with the `ai` feature.
//! - Check if a player move valid.
//! - Calculate the score of an expression.
//!
//! If you are looking for a server implementation, see the `board-server` crate.
#[cfg(feature = "ai")]
pub mod ai;

View File

@@ -39,20 +39,17 @@ pub fn parse(tokens: &[Token]) -> Result<Expressions, ()> {
fn parse_primary<'a>(
tokens: &mut Peekable<impl Iterator<Item = &'a Token>>,
) -> Result<Expression, ()> {
if let Some(Token::NumberLiteral(value)) = tokens.peek() {
tokens.next();
Ok(Expression::Digit(*value))
} else if let Some(Token::LeftParen) = tokens.peek() {
tokens.next();
let expr = parse_term(tokens)?;
if let Some(Token::RightParen) = tokens.peek() {
tokens.next();
Ok(Expression::Parentheses(Box::new(expr)))
} else {
Err(())
match tokens.next() {
Some(Token::NumberLiteral(value)) => Ok(Expression::Digit(*value)),
Some(Token::LeftParen) => {
let expr = parse_term(tokens)?;
if let Some(Token::RightParen) = tokens.next() {
Ok(Expression::Parentheses(Box::new(expr)))
} else {
Err(())
}
}
} else {
Err(())
_ => Err(()),
}
}
@@ -104,7 +101,7 @@ mod tests {
#[test]
fn test_parse() {
let tokens = vec![
let tokens = [
Token::NumberLiteral(1),
Token::Operator(Operator::Add),
Token::NumberLiteral(2),
@@ -128,7 +125,7 @@ mod tests {
#[test]
fn test_parse_reverse() {
let tokens = vec![
let tokens = [
Token::NumberLiteral(2),
Token::Operator(Operator::Multiply),
Token::NumberLiteral(3),
@@ -152,7 +149,7 @@ mod tests {
#[test]
fn test_parse_parentheses() {
let tokens = vec![
let tokens = [
Token::LeftParen,
Token::NumberLiteral(1),
Token::Operator(Operator::Add),
@@ -178,7 +175,7 @@ mod tests {
#[test]
fn test_parse_equals() {
let tokens = vec![
let tokens = [
Token::NumberLiteral(1),
Token::Equals,
Token::NumberLiteral(2),
@@ -198,7 +195,7 @@ mod tests {
#[test]
fn test_parse_unary_and_binary_minus() {
let tokens = vec![
let tokens = [
Token::Operator(Operator::Subtract),
Token::NumberLiteral(1),
Token::Operator(Operator::Subtract),
@@ -220,7 +217,7 @@ mod tests {
#[test]
fn test_parse_unary_before_parenthesis() {
let tokens = vec![
let tokens = [
Token::Operator(Operator::Subtract),
Token::LeftParen,
Token::NumberLiteral(9),
@@ -244,7 +241,7 @@ mod tests {
#[test]
fn test_double_unary() {
let tokens = vec![
let tokens = [
Token::Operator(Operator::Subtract),
Token::Operator(Operator::Subtract),
Token::NumberLiteral(7),

View File

@@ -1,5 +1,6 @@
/// A position in a 2d grid.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Position2d {
pub x: usize,
pub y: usize,

View File

@@ -14,14 +14,10 @@ fn calc_expression_score(tiles: &[Tile]) -> u32 {
for token in tiles {
match token {
Tile::Digit(_) => digit_score += 1,
Tile::Operator(op) => {
match op {
Operator::Add | Operator::Subtract => multiplier = 2,
Operator::Multiply => multiplier = 3,
Operator::Divide => digit_score += 10,
};
}
_ => unreachable!(),
Tile::Operator(Operator::Add | Operator::Subtract) => multiplier = 2,
Tile::Operator(Operator::Multiply) => multiplier = 3,
Tile::Operator(Operator::Divide) => digit_score += 10,
Tile::Equals => unreachable!(),
}
}
digit_score * multiplier

View File

@@ -1,8 +1,10 @@
use enum_map::Enum;
use std::fmt;
use std::str::FromStr;
/// A single digit that can be wrapped in parentheses.
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Digit {
pub value: i8,
pub has_left_parenthesis: bool,
@@ -61,6 +63,7 @@ impl TryFrom<&str> for Digit {
/// An operator that can be applied between two terms.
#[derive(Debug, PartialEq, Eq, Copy, Clone, Enum)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Operator {
Add,
Subtract,
@@ -104,6 +107,7 @@ impl TryFrom<char> for Operator {
/// A single piece of a mathematical expression.
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Tile {
Digit(Digit),
Operator(Operator),
@@ -114,6 +118,14 @@ impl TryFrom<&str> for Tile {
type Error = ();
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::from_str(value)
}
}
impl FromStr for Tile {
type Err = ();
fn from_str(value: &str) -> Result<Self, Self::Err> {
if let Ok(digit) = Digit::try_from(value) {
return Ok(Tile::Digit(digit));
}