-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 5814f62
Showing
27 changed files
with
1,532 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.vscode/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[build] | ||
target = "wasm32-unknown-unknown" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.idea/ | ||
|
||
target/ |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
[package] | ||
name = "board-n-pieces-plugin" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[lib] | ||
crate-type = ["cdylib"] | ||
|
||
[dependencies] | ||
wasm-minimal-protocol = { git = "https://github.com/astrale-sharp/wasm-minimal-protocol", rev = "637508c" } | ||
|
||
[profile.release] | ||
lto = true | ||
strip = true | ||
opt-level = "z" | ||
codegen-units = 1 | ||
panic = "abort" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
use std::borrow::Cow; | ||
use std::iter; | ||
|
||
pub type Result<T> = std::result::Result<T, String>; | ||
|
||
pub trait Abi: Sized { | ||
fn descriptor() -> Cow<'static, str>; | ||
|
||
fn from_bytes(bytes: &mut impl Iterator<Item = u8>) -> Result<Self>; | ||
|
||
fn from_slice(slice: &[u8]) -> Result<Self> { | ||
let mut iterator = slice.iter().copied(); | ||
let value = Self::from_bytes(&mut iterator)?; | ||
if iterator.next().is_some() { | ||
Err(format!( | ||
"Too many bytes when reading {}", | ||
Self::descriptor() | ||
))? | ||
} | ||
Ok(value) | ||
} | ||
|
||
fn to_bytes(self) -> impl IntoIterator<Item = u8>; | ||
|
||
fn to_vec(self) -> Vec<u8> { | ||
self.to_bytes().into_iter().collect() | ||
} | ||
} | ||
|
||
pub trait ByteAbi: Sized { | ||
fn descriptor() -> Cow<'static, str>; | ||
|
||
fn from_byte(byte: u8) -> Result<Self>; | ||
|
||
fn to_byte(self) -> u8; | ||
} | ||
|
||
impl<T: ByteAbi> Abi for T { | ||
fn descriptor() -> Cow<'static, str> { | ||
T::descriptor() | ||
} | ||
|
||
fn from_bytes(bytes: &mut impl Iterator<Item = u8>) -> Result<Self> { | ||
let byte = bytes | ||
.next() | ||
.ok_or(format!("Expected byte for {}", Self::descriptor()))?; | ||
Self::from_byte(byte) | ||
} | ||
|
||
fn to_bytes(self) -> impl IntoIterator<Item = u8> { | ||
[self.to_byte()] | ||
} | ||
} | ||
|
||
pub trait Decode<T> { | ||
fn decode(&self) -> Result<T>; | ||
} | ||
|
||
impl<T: Abi> Decode<T> for [u8] { | ||
fn decode(&self) -> Result<T> { | ||
T::from_slice(self) | ||
} | ||
} | ||
|
||
impl<T: ByteAbi> Decode<T> for u8 { | ||
fn decode(&self) -> Result<T> { | ||
T::from_byte(*self) | ||
} | ||
} | ||
|
||
impl ByteAbi for u8 { | ||
fn descriptor() -> Cow<'static, str> { | ||
"u8".into() | ||
} | ||
|
||
fn from_byte(byte: u8) -> Result<Self> { | ||
Ok(byte) | ||
} | ||
|
||
fn to_byte(self) -> u8 { | ||
self | ||
} | ||
} | ||
|
||
impl Abi for u32 { | ||
fn descriptor() -> Cow<'static, str> { | ||
"u32".into() | ||
} | ||
|
||
fn from_bytes(bytes: &mut impl Iterator<Item = u8>) -> Result<Self> { | ||
let [b0, b1, b2, b3] = bytes | ||
.take((Self::BITS / u8::BITS) as usize) | ||
.collect::<Vec<_>>()[..] | ||
else { | ||
Err("Expected 4 bytes for u32")? | ||
}; | ||
Ok(Self::from_be_bytes([b0, b1, b2, b3])) | ||
} | ||
|
||
fn to_bytes(self) -> impl IntoIterator<Item = u8> { | ||
self.to_be_bytes() | ||
} | ||
} | ||
|
||
impl<T: Abi> Abi for Option<T> { | ||
fn descriptor() -> Cow<'static, str> { | ||
format!("optional {}", T::descriptor()).into() | ||
} | ||
|
||
fn from_bytes(bytes: &mut impl Iterator<Item = u8>) -> Result<Self> { | ||
if bytes.next().ok_or("Expected byte")? == 0 { | ||
Ok(None) | ||
} else { | ||
Ok(Some(T::from_bytes(bytes)?)) | ||
} | ||
} | ||
|
||
fn to_bytes(self) -> impl IntoIterator<Item = u8> { | ||
match self { | ||
None => vec![0], | ||
Some(v) => iter::once(0).chain(v.to_bytes()).collect(), | ||
} | ||
} | ||
} | ||
|
||
#[macro_export] | ||
macro_rules! abi { | ||
( | ||
$type:ty as $name:literal { | ||
$( $field:ident ), * $(,)? | ||
} | ||
) => { | ||
impl $crate::abi::Abi for $type { | ||
fn descriptor() -> Cow<'static, str> { | ||
$name.into() | ||
} | ||
|
||
fn from_bytes(bytes: &mut impl Iterator<Item = u8>) -> abi::Result<Self> { | ||
Ok(Self { | ||
$( $field: <_ as $crate::abi::Abi>::from_bytes(bytes)?, ) + | ||
}) | ||
} | ||
|
||
fn to_bytes(self) -> impl Iterator<Item = u8> { | ||
iter::empty() | ||
$( .chain($crate::abi::Abi::to_bytes(self.$field)) ) + | ||
} | ||
} | ||
}; | ||
|
||
( | ||
$type:ty { | ||
$( $field:ident ), * $(,)? | ||
} | ||
) => { | ||
impl $crate::abi::Abi for $type { | ||
fn descriptor() -> Cow<'static, str> { | ||
stringify!($type).into() | ||
} | ||
|
||
fn from_bytes(bytes: &mut impl Iterator<Item = u8>) -> abi::Result<Self> { | ||
Ok(Self { | ||
$( $field: <_ as $crate::abi::Abi>::from_bytes(bytes)?, ) + | ||
}) | ||
} | ||
|
||
fn to_bytes(self) -> impl Iterator<Item = u8> { | ||
iter::empty() | ||
$( .chain($crate::abi::Abi::to_bytes(self.$field)) ) + | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
use crate::abi; | ||
use crate::abi::{Abi, ByteAbi}; | ||
use crate::model::{Board, CastlingAvailabilities, Color, Piece, Position, Square, SquareContent}; | ||
use crate::utils::SliceExt; | ||
use std::iter; | ||
use std::str::FromStr; | ||
|
||
fn parse_board(fen: &[u8]) -> abi::Result<Board> { | ||
let mut squares = Vec::new(); | ||
for fen_rank in fen.split_on(b'/') { | ||
let fen_rank: &[u8] = fen_rank; | ||
let mut rank = Vec::new(); | ||
for c in fen_rank { | ||
if let Some(n) = (*c as char).to_digit(10) { | ||
rank.extend(iter::repeat(SquareContent::Empty).take(n as usize)) | ||
} else { | ||
rank.push(SquareContent::Piece(Piece::from_byte(*c)?)) | ||
} | ||
} | ||
squares.push(rank) | ||
} | ||
squares.reverse(); | ||
Ok(Board::new(squares)) | ||
} | ||
|
||
fn parse_castling_availabilities(fen: &[u8]) -> abi::Result<CastlingAvailabilities> { | ||
Ok(CastlingAvailabilities { | ||
white_king_side: fen.contains(&b'K'), | ||
white_queen_side: fen.contains(&b'Q'), | ||
black_king_side: fen.contains(&b'k'), | ||
black_queen_side: fen.contains(&b'q'), | ||
}) | ||
} | ||
|
||
fn parse_en_passant_target_square(fen: &[u8]) -> abi::Result<Option<Square>> { | ||
if fen == b"-" { | ||
return Ok(None); | ||
} | ||
Ok(Some(Square::from_slice(fen)?)) | ||
} | ||
|
||
fn parse_int(fen: &[u8]) -> abi::Result<u32> { | ||
u32::from_str( | ||
std::str::from_utf8(fen) | ||
.map_err(|_| "Unreachable: FEN was already checked to be valid ASCII.")?, | ||
) | ||
.map_err(|err| err.to_string()) | ||
} | ||
|
||
pub fn parse_fen(fen: &[u8]) -> abi::Result<Position> { | ||
if !fen.is_ascii() { | ||
Err("Invalid FEN: expected ASCII")? | ||
} | ||
|
||
let mut parts = fen.split_on(b' ').peekable(); | ||
|
||
let board = parse_board(parts.next().ok_or("Invalid FEN: missing board info")?)?; | ||
|
||
if parts.peek().is_none() { | ||
return Ok(Position::default_with_board(board)); | ||
} | ||
|
||
let active = Color::from_slice(parts.next().ok_or("Invalid FEN: missing active player")?)?; | ||
|
||
let castling_availabilities = parse_castling_availabilities( | ||
parts | ||
.next() | ||
.ok_or("Invalid FEN: missing castling availabilities")?, | ||
)?; | ||
|
||
let en_passant_target_square = parse_en_passant_target_square( | ||
parts | ||
.next() | ||
.ok_or("Invalid FEN: missing en passant target square")?, | ||
)?; | ||
|
||
let halfmove = parse_int(parts.next().ok_or("Invalid FEN: expected fullmove")?)?; | ||
|
||
let fullmove = parse_int(parts.next().ok_or("Invalid FEN: expected fullmove")?)?; | ||
|
||
if parts.next().is_some() { | ||
Err("Invalid FEN: too many parts")? | ||
} | ||
|
||
Ok(Position { | ||
board, | ||
active, | ||
castling_availabilities, | ||
en_passant_target_square, | ||
halfmove, | ||
fullmove, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
mod abi; | ||
mod fen; | ||
mod model; | ||
mod utils; | ||
|
||
use crate::abi::Abi; | ||
use crate::model::Position; | ||
use wasm_minimal_protocol::{initiate_protocol, wasm_func}; | ||
|
||
initiate_protocol!(); | ||
|
||
#[wasm_func] | ||
pub fn starting_position() -> Vec<u8> { | ||
Position::default().to_vec() | ||
} | ||
|
||
#[wasm_func] | ||
pub fn parse_fen(fen: &[u8]) -> abi::Result<Vec<u8>> { | ||
Ok(fen::parse_fen(fen)?.to_vec()) | ||
} |
Oops, something went wrong.