diff --git a/Cargo.toml b/Cargo.toml index 7ac6f6a..64233cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,7 @@ [workspace] resolver = "2" + +# members share a lockfile in the root of the repo members = [ "mino", "mino-code-gen", @@ -7,5 +9,10 @@ members = [ "tidepool", ] +# excluded packages get their own lockfiles +exclude = [ + "fish-webworker", +] + [profile.release] panic = "abort" diff --git a/fish-webworker/.gitignore b/fish-webworker/.gitignore new file mode 100644 index 0000000..620f295 --- /dev/null +++ b/fish-webworker/.gitignore @@ -0,0 +1,5 @@ +target/ +build/ +node_modules/ +blockfish.js +blockfish.wasm diff --git a/fish-webworker/Cargo.lock b/fish-webworker/Cargo.lock new file mode 100644 index 0000000..51a35f4 --- /dev/null +++ b/fish-webworker/Cargo.lock @@ -0,0 +1,296 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "anyhow" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" + +[[package]] +name = "bumpalo" +version = "3.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "fish" +version = "0.1.0" +dependencies = [ + "ahash", + "bumpalo", + "hashbrown", + "mino", + "smallvec", + "tracing", +] + +[[package]] +name = "fish-webworker" +version = "0.1.0" +dependencies = [ + "console_error_panic_hook", + "fish", + "mino", + "wasm-bindgen", + "wasm-bindgen-macro", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "mino" +version = "0.1.0" +dependencies = [ + "mino-code-gen", +] + +[[package]] +name = "mino-code-gen" +version = "0.1.0" +dependencies = [ + "anyhow", + "serde", + "serde_json", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "syn" +version = "2.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/fish-webworker/Cargo.toml b/fish-webworker/Cargo.toml new file mode 100644 index 0000000..291665e --- /dev/null +++ b/fish-webworker/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "fish-webworker" +description = "Blockfish engine webworker" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +fish = { path = "../fish" } +mino = { path = "../mino" } + +wasm-bindgen = "0.2" +wasm-bindgen-macro = "0.2" +console_error_panic_hook = "0.1.7" diff --git a/fish-webworker/package-lock.json b/fish-webworker/package-lock.json new file mode 100644 index 0000000..6f22449 --- /dev/null +++ b/fish-webworker/package-lock.json @@ -0,0 +1,68 @@ +{ + "name": "@blockfish/webworker", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@blockfish/webworker", + "version": "1.0.0", + "license": "LGPL", + "dependencies": { + "esbuild": "^0.20.1" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.1.tgz", + "integrity": "sha512-5gRPk7pKuaIB+tmH+yKd2aQTRpqlf1E4f/mC+tawIm/CGJemZcHZpp2ic8oD83nKgUPMEd0fNanrnFljiruuyA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.1.tgz", + "integrity": "sha512-OJwEgrpWm/PCMsLVWXKqvcjme3bHNpOgN7Tb6cQnR5n0TPbQx1/Xrn7rqM+wn17bYeT6MGB5sn1Bh5YiGi70nA==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.1", + "@esbuild/android-arm": "0.20.1", + "@esbuild/android-arm64": "0.20.1", + "@esbuild/android-x64": "0.20.1", + "@esbuild/darwin-arm64": "0.20.1", + "@esbuild/darwin-x64": "0.20.1", + "@esbuild/freebsd-arm64": "0.20.1", + "@esbuild/freebsd-x64": "0.20.1", + "@esbuild/linux-arm": "0.20.1", + "@esbuild/linux-arm64": "0.20.1", + "@esbuild/linux-ia32": "0.20.1", + "@esbuild/linux-loong64": "0.20.1", + "@esbuild/linux-mips64el": "0.20.1", + "@esbuild/linux-ppc64": "0.20.1", + "@esbuild/linux-riscv64": "0.20.1", + "@esbuild/linux-s390x": "0.20.1", + "@esbuild/linux-x64": "0.20.1", + "@esbuild/netbsd-x64": "0.20.1", + "@esbuild/openbsd-x64": "0.20.1", + "@esbuild/sunos-x64": "0.20.1", + "@esbuild/win32-arm64": "0.20.1", + "@esbuild/win32-ia32": "0.20.1", + "@esbuild/win32-x64": "0.20.1" + } + } + } +} diff --git a/fish-webworker/package.json b/fish-webworker/package.json new file mode 100644 index 0000000..f896db0 --- /dev/null +++ b/fish-webworker/package.json @@ -0,0 +1,16 @@ +{ + "name": "@blockfish/webworker", + "version": "1.0.0", + "description": "Blockfish engine webworker", + "main": "blockfish.js", + "scripts": { + "prebuild": "cargo build --release --target wasm32-unknown-unknown && wasm-bindgen target/wasm32-unknown-unknown/release/fish_webworker.wasm --out-dir build --target web --no-typescript", + "build": "npx esbuild src/worker.js --bundle --minify --outdir=build", + "postbuild": "cp build/fish_webworker_bg.wasm ./blockfish.wasm && cp build/worker.js ./blockfish.js" + }, + "author": "iitalics", + "license": "LGPL", + "dependencies": { + "esbuild": "^0.20.1" + } +} diff --git a/fish-webworker/src/lib.rs b/fish-webworker/src/lib.rs new file mode 100644 index 0000000..9826641 --- /dev/null +++ b/fish-webworker/src/lib.rs @@ -0,0 +1,84 @@ +use wasm_bindgen_macro::wasm_bindgen; + +use fish::{Bot, Weights}; +use mino::srs::{PieceType, Queue}; +use mino::MatBuf; + +struct Config { + weights: Weights, + max_iters: u32, +} + +static CONFIG: Config = Config { + weights: Weights::DEFAULT, + max_iters: 50_000, +}; + +#[wasm_bindgen(start)] +fn start() { + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); +} + +#[wasm_bindgen] +pub fn get_version() -> String { + "0.0.x".to_owned() +} + +#[wasm_bindgen] +pub fn get_config() -> Vec { + let mut config = Vec::from(CONFIG.weights.0); + config.push(Weights::PER_PIECE); + config.push(CONFIG.max_iters as i32); + config +} + +#[wasm_bindgen] +pub fn suggest(matrix: String, hold: String, next: String) -> Result, String> { + let matrix = parse_matrix(&matrix); + let hold = parse_hold(&hold)?; + let next = parse_queue(&next)?; + let queue = Queue::new(hold, &next); + + let res = { + let mut bot = Bot::new(&CONFIG.weights, &matrix, queue); + bot.think(CONFIG.max_iters); + bot.suggest().ok_or("No suggestion found")? + }; + + Ok(vec![ + res.loc.x as i16 as i32, + res.loc.y as i16 as i32, + res.loc.r as i8 as i32, + res.ty as u8 as i32, + ]) +} + +fn parse_matrix(s: &str) -> MatBuf { + s.bytes() + .take(400) + .enumerate() + .filter(|(_, c)| *c != b' ') + .map(|(i, _)| ((i % 10) as i16, (i / 10) as i16)) + .fold(MatBuf::new(), |mut mat, (x, y)| { + mat.set(x, y); + mat + }) +} + +fn parse_hold(s: &str) -> Result, &'static str> { + match s.len() { + 0 => Ok(None), + 1 => { + let c = s.as_bytes()[0]; + let ty = PieceType::try_from(c as char).map_err(|_| "Unrecognized hold piece")?; + Ok(Some(ty)) + } + _ => Err("Expected hold piece to be a single character"), + } +} + +fn parse_queue(s: &str) -> Result, &'static str> { + s.bytes() + .map(|c| PieceType::try_from(c as char).map_err(|_| "Unrecognized next piece")) + .collect() +} diff --git a/fish-webworker/src/worker.js b/fish-webworker/src/worker.js new file mode 100644 index 0000000..01136b7 --- /dev/null +++ b/fish-webworker/src/worker.js @@ -0,0 +1,46 @@ +import { + default as init, + get_version as getVersion, + get_config as getConfig, + suggest, +} from '../build/fish_webworker.js' + +let initPromise = init('/blockfish.wasm'); + +async function handle(msg) { + switch (msg.type) { + case 'init': + { + let version = getVersion(); + let config = Array.from(getConfig()); + let maxIters = config.pop(); + let weightFactor = config.pop(); + let weights = config.map(w => w / weightFactor); + return { version, config: { weights, maxIters } }; + } + + case 'suggest': + { + let [x, y, r, type] = suggest(msg.state.matrix, msg.state.hold, msg.state.next); + r = ['spawn', 'right', 'reverse', 'left'][r]; + type = 'IJLOSTZ'[type]; + return { x, y, r, type }; + } + + default: + throw `Unrecognized message "${msg.type}"`; + } +} + +self.addEventListener('message', async e => { + await initPromise; + + let resp; + try { + resp = await handle(e.data); + } catch (e) { + resp = { error: e.toString() }; + } + + self.postMessage(resp); +}); diff --git a/fish/src/lib.rs b/fish/src/lib.rs index 97273c7..28c80e7 100644 --- a/fish/src/lib.rs +++ b/fish/src/lib.rs @@ -10,3 +10,6 @@ type HashMap = hashbrown::HashMap; type HashSet = hashbrown::HashSet; type HashBuilder = core::hash::BuildHasherDefault; type Arena = bumpalo::Bump; + +pub use bot::{Bot, Metrics}; +pub use eval::Weights;