Include a serde feature in the shared crate
This commit is contained in:
17
.drone.yml
17
.drone.yml
@@ -4,26 +4,21 @@ name: default
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: rust:1.68
|
||||
image: rust:1.77
|
||||
commands:
|
||||
- cargo build
|
||||
volumes:
|
||||
volumes: &caches
|
||||
- name: cargo-cache
|
||||
path: /cache/cargo
|
||||
environment:
|
||||
environment: &env
|
||||
CARGO_HOME: /cache/cargo
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
|
||||
- name: test
|
||||
image: rust:1.68
|
||||
image: rust:1.77
|
||||
commands:
|
||||
- cargo test --all-features
|
||||
volumes:
|
||||
- name: cargo-cache
|
||||
path: /cache/cargo
|
||||
environment:
|
||||
CARGO_HOME: /cache/cargo
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
volumes: *caches
|
||||
environment: *env
|
||||
depends_on:
|
||||
- build
|
||||
|
||||
|
@@ -3,6 +3,7 @@ strip = true
|
||||
panic = "abort"
|
||||
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"board-shared",
|
||||
"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
|
||||
[dependencies]
|
||||
yew = { version="0.20", features=["csr"] }
|
||||
yew = { version = "0.21.0", features=["csr"] }
|
||||
board-shared = { path = "../board-shared" }
|
||||
board-network = { path = "../board-network" }
|
||||
gloo-dialogs = "0.1.1"
|
||||
gloo-dialogs = "0.2.0"
|
||||
getrandom = { version = "0.2.8", features = ["js"] }
|
||||
gloo-net = "0.2.6"
|
||||
gloo-net = "0.5.0"
|
||||
futures = "0.3.26"
|
||||
serde_json = "1.0.93"
|
||||
gloo-utils = "0.1.6"
|
||||
gloo-utils = "0.2.0"
|
||||
web-sys = "0.3.61"
|
||||
|
@@ -1,7 +1,7 @@
|
||||
use crate::tile_view::TileView;
|
||||
use board_shared::game::Hand;
|
||||
use yew::html;
|
||||
use yew::prelude::*;
|
||||
use yew::{html, Callback, Html};
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct HandViewProps {
|
||||
|
@@ -43,7 +43,7 @@ pub fn remote_game_view(
|
||||
}: &RemoteGameViewProps,
|
||||
) -> Html {
|
||||
macro_rules! send_client_message {
|
||||
($write:expr, $message:expr) => {{
|
||||
($write:ident, $message:expr) => {{
|
||||
let write = $write.clone();
|
||||
spawn_local(async move {
|
||||
write
|
||||
@@ -68,8 +68,7 @@ pub fn remote_game_view(
|
||||
let player_name = player_name.clone();
|
||||
let room_name = room_name.clone();
|
||||
let write = write.clone();
|
||||
use_effect_with_deps(
|
||||
move |_| {
|
||||
use_effect(move || {
|
||||
send_client_message!(
|
||||
write,
|
||||
if let Some(room_name) = room_name {
|
||||
@@ -78,15 +77,12 @@ pub fn remote_game_view(
|
||||
ClientMessage::CreateRoom(player_name)
|
||||
}
|
||||
);
|
||||
},
|
||||
(),
|
||||
);
|
||||
});
|
||||
|
||||
let is_started = is_started.clone();
|
||||
let current_player_turn = current_player_turn.clone();
|
||||
let read = read.clone();
|
||||
use_effect_with_deps(
|
||||
move |_| {
|
||||
use_effect(move || {
|
||||
spawn_local(async move {
|
||||
while let Some(event) = read.borrow_mut().next().await {
|
||||
if let Message::Text(msg) = event.unwrap() {
|
||||
@@ -104,8 +100,7 @@ pub fn remote_game_view(
|
||||
is_started.set(true);
|
||||
}
|
||||
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();
|
||||
@@ -120,9 +115,7 @@ pub fn remote_game_view(
|
||||
}
|
||||
});
|
||||
|| {}
|
||||
},
|
||||
(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
let on_tile_select = {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
use board_shared::tile::Tile;
|
||||
use yew::html;
|
||||
use yew::prelude::*;
|
||||
use yew::{html, Callback, Html};
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct PlacedTileViewProps {
|
||||
@@ -54,6 +54,6 @@ pub fn tile_view(
|
||||
html! {
|
||||
<div class="tile" onclick={Callback::from(move |_| {
|
||||
on_select.emit(idx)
|
||||
})}>{ tile }</div>
|
||||
})}>{ tile.to_string() }</div>
|
||||
}
|
||||
}
|
||||
|
@@ -1,2 +1 @@
|
||||
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};
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// 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.
|
||||
TilePlaceEqual(Position2dRef),
|
||||
TilePlaceEqual(Position2d),
|
||||
/// 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.
|
||||
Validate,
|
||||
}
|
||||
@@ -35,7 +37,7 @@ pub enum ServerMessage {
|
||||
JoinedRoom {
|
||||
room_name: String,
|
||||
players: Vec<(String, u32, bool)>,
|
||||
board: BoardRef,
|
||||
board: Board,
|
||||
active_player: usize,
|
||||
has_started: bool,
|
||||
},
|
||||
@@ -50,13 +52,13 @@ pub enum ServerMessage {
|
||||
/// Change the current player
|
||||
PlayerTurn(usize),
|
||||
/// Update the current hand of the player
|
||||
SyncHand(Vec<TileRef>),
|
||||
SyncHand(Vec<Tile>),
|
||||
/// Update the score of the n-th player.
|
||||
SyncScore(usize, u32),
|
||||
/// Informs that a tile has been placed
|
||||
TilePlaced(Position2dRef, TileRef),
|
||||
TilePlaced(Position2d, Tile),
|
||||
/// Informs that a tile has been removed
|
||||
TileRemoved(Position2dRef),
|
||||
TileRemoved(Position2d),
|
||||
TurnRejected(String),
|
||||
/// Notify that the game has ended.
|
||||
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]
|
||||
futures = { version = "0.3" }
|
||||
board-shared = { path = "../board-shared" }
|
||||
board-shared = { path = "../board-shared", features = ["serde"] }
|
||||
board-network = { path = "../board-network" }
|
||||
smol = "1.3.0"
|
||||
async-tungstenite = "0.20.0"
|
||||
tungstenite = "0.18.0"
|
||||
smol = "2.0.0"
|
||||
async-tungstenite = "0.25.0"
|
||||
tungstenite = "0.21.0"
|
||||
anyhow = "1.0.69"
|
||||
rand = "0.8.5"
|
||||
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"
|
||||
|
@@ -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
|
||||
|
||||
# Build with musl to statically link
|
||||
|
@@ -29,13 +29,13 @@ impl RedisLeaderboard {
|
||||
#[async_trait]
|
||||
impl Leaderboard for RedisLeaderboard {
|
||||
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?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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 leaderboard: Vec<LeaderboardEntry> = con
|
||||
.zrange_withscores(LEADERBOARD, 0, (count - 1).min(LEADERBOARD_SIZE))
|
||||
|
@@ -1,7 +1,6 @@
|
||||
use crate::leaderboard::{InMemoryLeaderboard, Leaderboard};
|
||||
use crate::player::Player;
|
||||
use board_network::protocol::{ClientMessage, ServerMessage};
|
||||
use board_network::types::TileRef;
|
||||
use board_shared::board::Board;
|
||||
use board_shared::deck::RngDeck;
|
||||
use board_shared::expr::is_valid_guess;
|
||||
@@ -87,7 +86,7 @@ impl Room {
|
||||
.iter()
|
||||
.map(|p| (p.name.clone(), p.score, p.ws.is_some()))
|
||||
.collect(),
|
||||
board: (&self.board).into(),
|
||||
board: self.board.clone(),
|
||||
active_player: self.active_player,
|
||||
has_started: self.has_started,
|
||||
})?;
|
||||
@@ -328,13 +327,7 @@ impl Room {
|
||||
fn sync_hand(&mut self, player_id: usize) {
|
||||
self.send(
|
||||
player_id,
|
||||
ServerMessage::SyncHand(
|
||||
self.players[player_id]
|
||||
.hand
|
||||
.iter()
|
||||
.map(|t| <Tile as Into<TileRef>>::into(*t))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
ServerMessage::SyncHand(self.players[player_id].hand.tiles.clone()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -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"]
|
||||
|
@@ -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() {
|
||||
|
@@ -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> {
|
||||
|
@@ -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,
|
||||
|
@@ -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;
|
||||
|
@@ -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();
|
||||
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.peek() {
|
||||
tokens.next();
|
||||
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),
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
|
@@ -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));
|
||||
}
|
||||
|
Reference in New Issue
Block a user