Include a serde feature in the shared crate
continuous-integration/drone/push Build is passing Details

main
Clément FRÉVILLE 1 year ago
parent 3d93bf0fda
commit f1962503b8

@ -4,26 +4,21 @@ name: default
steps: steps:
- name: build - name: build
image: rust:1.68 image: rust:1.77
commands: commands:
- cargo build - cargo build
volumes: volumes: &caches
- name: cargo-cache - name: cargo-cache
path: /cache/cargo path: /cache/cargo
environment: environment: &env
CARGO_HOME: /cache/cargo CARGO_HOME: /cache/cargo
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
- name: test - name: test
image: rust:1.68 image: rust:1.77
commands: commands:
- cargo test --all-features - cargo test --all-features
volumes: volumes: *caches
- name: cargo-cache environment: *env
path: /cache/cargo
environment:
CARGO_HOME: /cache/cargo
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
depends_on: depends_on:
- build - build

@ -3,6 +3,7 @@ strip = true
panic = "abort" panic = "abort"
[workspace] [workspace]
resolver = "2"
members = [ members = [
"board-shared", "board-shared",
"board-frontend", "board-frontend",

@ -8,13 +8,13 @@ categories = ["gui", "wasm", "web-programming"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
yew = { version="0.20", features=["csr"] } yew = { version = "0.21.0", features=["csr"] }
board-shared = { path = "../board-shared" } board-shared = { path = "../board-shared" }
board-network = { path = "../board-network" } board-network = { path = "../board-network" }
gloo-dialogs = "0.1.1" gloo-dialogs = "0.2.0"
getrandom = { version = "0.2.8", features = ["js"] } getrandom = { version = "0.2.8", features = ["js"] }
gloo-net = "0.2.6" gloo-net = "0.5.0"
futures = "0.3.26" futures = "0.3.26"
serde_json = "1.0.93" serde_json = "1.0.93"
gloo-utils = "0.1.6" gloo-utils = "0.2.0"
web-sys = "0.3.61" web-sys = "0.3.61"

@ -1,7 +1,7 @@
use crate::tile_view::TileView; use crate::tile_view::TileView;
use board_shared::game::Hand; use board_shared::game::Hand;
use yew::html;
use yew::prelude::*; use yew::prelude::*;
use yew::{html, Callback, Html};
#[derive(Properties, PartialEq)] #[derive(Properties, PartialEq)]
pub struct HandViewProps { pub struct HandViewProps {

@ -43,7 +43,7 @@ pub fn remote_game_view(
}: &RemoteGameViewProps, }: &RemoteGameViewProps,
) -> Html { ) -> Html {
macro_rules! send_client_message { macro_rules! send_client_message {
($write:expr, $message:expr) => {{ ($write:ident, $message:expr) => {{
let write = $write.clone(); let write = $write.clone();
spawn_local(async move { spawn_local(async move {
write write
@ -68,61 +68,54 @@ pub fn remote_game_view(
let player_name = player_name.clone(); let player_name = player_name.clone();
let room_name = room_name.clone(); let room_name = room_name.clone();
let write = write.clone(); let write = write.clone();
use_effect_with_deps( use_effect(move || {
move |_| { send_client_message!(
send_client_message!( write,
write, if let Some(room_name) = room_name {
if let Some(room_name) = room_name { ClientMessage::JoinRoom(room_name, player_name)
ClientMessage::JoinRoom(room_name, player_name) } else {
} else { ClientMessage::CreateRoom(player_name)
ClientMessage::CreateRoom(player_name) }
} );
); });
},
(),
);
let is_started = is_started.clone(); let is_started = is_started.clone();
let current_player_turn = current_player_turn.clone(); let current_player_turn = current_player_turn.clone();
let read = read.clone(); let read = read.clone();
use_effect_with_deps( use_effect(move || {
move |_| { spawn_local(async move {
spawn_local(async move { while let Some(event) = read.borrow_mut().next().await {
while let Some(event) = read.borrow_mut().next().await { if let Message::Text(msg) = event.unwrap() {
if let Message::Text(msg) = event.unwrap() { match serde_json::from_str::<ServerMessage>(&msg) {
match serde_json::from_str::<ServerMessage>(&msg) { Ok(ServerMessage::JoinedRoom {
Ok(ServerMessage::JoinedRoom { room_name,
room_name, has_started,
has_started, ..
.. }) => {
}) => { alert(&format!("Joined room {room_name}"));
alert(&format!("Joined room {room_name}")); is_started.set(has_started);
is_started.set(has_started); }
} Ok(ServerMessage::PlayerTurn(player_id)) => {
Ok(ServerMessage::PlayerTurn(player_id)) => { current_player_turn.set(player_id);
current_player_turn.set(player_id); is_started.set(true);
is_started.set(true); }
} Ok(ServerMessage::SyncHand(hand)) => {
Ok(ServerMessage::SyncHand(hand)) => { in_hand.set(Hand::new(hand.iter().map(|&x| x.into()).collect()));
in_hand }
.set(Hand::new(hand.iter().map(|&x| x.into()).collect())); Ok(ServerMessage::TilePlaced(pos, tile)) => {
} let mut changed = board.deref().clone();
Ok(ServerMessage::TilePlaced(pos, tile)) => { changed.set(pos.x, pos.y, tile.into());
let mut changed = board.deref().clone(); board.set(changed);
changed.set(pos.x, pos.y, tile.into()); }
board.set(changed); r => {
} alert(&format!("{r:?}"));
r => { }
alert(&format!("{r:?}")); };
}
};
}
} }
}); }
|| {} });
}, || {}
(), });
);
} }
let on_tile_select = { let on_tile_select = {

@ -1,6 +1,6 @@
use board_shared::tile::Tile; use board_shared::tile::Tile;
use yew::html;
use yew::prelude::*; use yew::prelude::*;
use yew::{html, Callback, Html};
#[derive(Properties, PartialEq)] #[derive(Properties, PartialEq)]
pub struct PlacedTileViewProps { pub struct PlacedTileViewProps {
@ -54,6 +54,6 @@ pub fn tile_view(
html! { html! {
<div class="tile" onclick={Callback::from(move |_| { <div class="tile" onclick={Callback::from(move |_| {
on_select.emit(idx) on_select.emit(idx)
})}>{ tile }</div> })}>{ tile.to_string() }</div>
} }
} }

@ -1,2 +1 @@
pub mod protocol; pub mod protocol;
pub mod types;

@ -1,4 +1,6 @@
use crate::types::{BoardRef, Position2dRef, TileRef}; use board_shared::board::Board;
use board_shared::position::Position2d;
use board_shared::tile::Tile;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// A message sent by the client to the server. /// A message sent by the client to the server.
@ -19,11 +21,11 @@ pub enum ClientMessage {
/// Try to place a tile from the hand on the board. /// Try to place a tile from the hand on the board.
/// ///
/// The server will validate the move and answer with a TilePlaced if the message is valid. /// The server will validate the move and answer with a TilePlaced if the message is valid.
TileUse(Position2dRef, usize), TileUse(Position2d, usize),
/// Try to place an equal sign on the board. /// Try to place an equal sign on the board.
TilePlaceEqual(Position2dRef), TilePlaceEqual(Position2d),
/// Try to remove a tile from the board to add it to the hand. /// Try to remove a tile from the board to add it to the hand.
TileTake(Position2dRef), TileTake(Position2d),
/// Get the server to validate the current player moves. /// Get the server to validate the current player moves.
Validate, Validate,
} }
@ -35,7 +37,7 @@ pub enum ServerMessage {
JoinedRoom { JoinedRoom {
room_name: String, room_name: String,
players: Vec<(String, u32, bool)>, players: Vec<(String, u32, bool)>,
board: BoardRef, board: Board,
active_player: usize, active_player: usize,
has_started: bool, has_started: bool,
}, },
@ -50,13 +52,13 @@ pub enum ServerMessage {
/// Change the current player /// Change the current player
PlayerTurn(usize), PlayerTurn(usize),
/// Update the current hand of the player /// Update the current hand of the player
SyncHand(Vec<TileRef>), SyncHand(Vec<Tile>),
/// Update the score of the n-th player. /// Update the score of the n-th player.
SyncScore(usize, u32), SyncScore(usize, u32),
/// Informs that a tile has been placed /// Informs that a tile has been placed
TilePlaced(Position2dRef, TileRef), TilePlaced(Position2d, Tile),
/// Informs that a tile has been removed /// Informs that a tile has been removed
TileRemoved(Position2dRef), TileRemoved(Position2d),
TurnRejected(String), TurnRejected(String),
/// Notify that the game has ended. /// Notify that the game has ended.
GameOver, GameOver,

@ -1,134 +0,0 @@
use board_shared::{
board::Board,
position::{Grid2d, Position2d},
tile::{Digit, Operator, Tile},
};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BoardRef {
pub tiles: Vec<Option<TileRef>>,
pub width: usize,
pub height: usize,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum TileRef {
Digit(DigitRef),
Operator(OperatorRef),
Equals,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct DigitRef {
pub value: i8,
pub has_left_parenthesis: bool,
pub has_right_parenthesis: bool,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum OperatorRef {
Add,
Subtract,
Multiply,
Divide,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Position2dRef {
pub x: usize,
pub y: usize,
}
impl From<&Board> for BoardRef {
fn from(value: &Board) -> Self {
Self {
tiles: value
.iter()
.map(|tile| tile.map(Into::into))
.collect::<Vec<_>>(),
width: value.width(),
height: value.height(),
}
}
}
impl From<Tile> for TileRef {
fn from(value: Tile) -> Self {
match value {
Tile::Digit(digit) => TileRef::Digit(digit.into()),
Tile::Operator(operator) => TileRef::Operator(operator.into()),
Tile::Equals => TileRef::Equals,
}
}
}
impl From<TileRef> for Tile {
fn from(value: TileRef) -> Self {
match value {
TileRef::Digit(digit) => Tile::Digit(digit.into()),
TileRef::Operator(operator) => Tile::Operator(operator.into()),
TileRef::Equals => Tile::Equals,
}
}
}
impl From<Digit> for DigitRef {
fn from(value: Digit) -> Self {
Self {
value: value.value,
has_left_parenthesis: value.has_left_parenthesis,
has_right_parenthesis: value.has_right_parenthesis,
}
}
}
impl From<DigitRef> for Digit {
fn from(value: DigitRef) -> Self {
Self {
value: value.value,
has_left_parenthesis: value.has_left_parenthesis,
has_right_parenthesis: value.has_right_parenthesis,
}
}
}
impl From<Operator> for OperatorRef {
fn from(value: Operator) -> Self {
match value {
Operator::Add => OperatorRef::Add,
Operator::Subtract => OperatorRef::Subtract,
Operator::Multiply => OperatorRef::Multiply,
Operator::Divide => OperatorRef::Divide,
}
}
}
impl From<OperatorRef> for Operator {
fn from(value: OperatorRef) -> Self {
match value {
OperatorRef::Add => Operator::Add,
OperatorRef::Subtract => Operator::Subtract,
OperatorRef::Multiply => Operator::Multiply,
OperatorRef::Divide => Operator::Divide,
}
}
}
impl From<Position2d> for Position2dRef {
fn from(value: Position2d) -> Self {
Self {
x: value.x,
y: value.y,
}
}
}
impl From<Position2dRef> for Position2d {
fn from(value: Position2dRef) -> Self {
Self {
x: value.x,
y: value.y,
}
}
}

@ -7,13 +7,13 @@ edition = "2021"
[dependencies] [dependencies]
futures = { version = "0.3" } futures = { version = "0.3" }
board-shared = { path = "../board-shared" } board-shared = { path = "../board-shared", features = ["serde"] }
board-network = { path = "../board-network" } board-network = { path = "../board-network" }
smol = "1.3.0" smol = "2.0.0"
async-tungstenite = "0.20.0" async-tungstenite = "0.25.0"
tungstenite = "0.18.0" tungstenite = "0.21.0"
anyhow = "1.0.69" anyhow = "1.0.69"
rand = "0.8.5" rand = "0.8.5"
serde_json = "1.0.93" serde_json = "1.0.93"
redis = { version = "0.22.3", features = ["aio", "async-std-comp"] } redis = { version = "0.25.2", features = ["aio", "async-std-comp"] }
async-trait = "0.1.66" async-trait = "0.1.66"

@ -1,6 +1,5 @@
FROM rust:1.68.0-slim as builder FROM rust:1.77.0-slim as builder
ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse
WORKDIR /usr/src/board WORKDIR /usr/src/board
# Build with musl to statically link # Build with musl to statically link

@ -29,13 +29,13 @@ impl RedisLeaderboard {
#[async_trait] #[async_trait]
impl Leaderboard for RedisLeaderboard { impl Leaderboard for RedisLeaderboard {
async fn add_score(&self, player_name: &str, score: u32) -> Result<(), RedisError> { async fn add_score(&self, player_name: &str, score: u32) -> Result<(), RedisError> {
let mut con = self.client.get_async_connection().await?; let mut con = self.client.get_multiplexed_async_connection().await?;
con.zadd(LEADERBOARD, player_name, score).await?; con.zadd(LEADERBOARD, player_name, score).await?;
Ok(()) Ok(())
} }
async fn get_highscores(&self) -> Result<Vec<LeaderboardEntry>, RedisError> { async fn get_highscores(&self) -> Result<Vec<LeaderboardEntry>, RedisError> {
let mut con = self.client.get_async_connection().await?; let mut con = self.client.get_multiplexed_async_connection().await?;
let count: isize = con.zcard(LEADERBOARD).await?; let count: isize = con.zcard(LEADERBOARD).await?;
let leaderboard: Vec<LeaderboardEntry> = con let leaderboard: Vec<LeaderboardEntry> = con
.zrange_withscores(LEADERBOARD, 0, (count - 1).min(LEADERBOARD_SIZE)) .zrange_withscores(LEADERBOARD, 0, (count - 1).min(LEADERBOARD_SIZE))

@ -1,7 +1,6 @@
use crate::leaderboard::{InMemoryLeaderboard, Leaderboard}; use crate::leaderboard::{InMemoryLeaderboard, Leaderboard};
use crate::player::Player; use crate::player::Player;
use board_network::protocol::{ClientMessage, ServerMessage}; use board_network::protocol::{ClientMessage, ServerMessage};
use board_network::types::TileRef;
use board_shared::board::Board; use board_shared::board::Board;
use board_shared::deck::RngDeck; use board_shared::deck::RngDeck;
use board_shared::expr::is_valid_guess; use board_shared::expr::is_valid_guess;
@ -87,7 +86,7 @@ impl Room {
.iter() .iter()
.map(|p| (p.name.clone(), p.score, p.ws.is_some())) .map(|p| (p.name.clone(), p.score, p.ws.is_some()))
.collect(), .collect(),
board: (&self.board).into(), board: self.board.clone(),
active_player: self.active_player, active_player: self.active_player,
has_started: self.has_started, has_started: self.has_started,
})?; })?;
@ -328,13 +327,7 @@ impl Room {
fn sync_hand(&mut self, player_id: usize) { fn sync_hand(&mut self, player_id: usize) {
self.send( self.send(
player_id, player_id,
ServerMessage::SyncHand( ServerMessage::SyncHand(self.players[player_id].hand.tiles.clone()),
self.players[player_id]
.hand
.iter()
.map(|t| <Tile as Into<TileRef>>::into(*t))
.collect::<Vec<_>>(),
),
); );
} }
} }

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

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

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

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

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

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

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

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

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

Loading…
Cancel
Save