feat(wasm): add wasm js generated files and basic web example
authorMA Beaudet <ma@beaudet.xyz>
Thu, 4 Nov 2021 16:24:57 +0000 (17:24 +0100)
committerMA Beaudet <ma@beaudet.xyz>
Thu, 4 Nov 2021 16:24:57 +0000 (17:24 +0100)
README.md
static/deploy.sh [new file with mode: 0644]
static/favicon.ico [new file with mode: 0644]
static/index.html [new file with mode: 0644]
static/index.js [new file with mode: 0644]
static/poker_eval.js [new file with mode: 0644]
static/poker_eval_bg.wasm [new file with mode: 0644]
static/styles.css [new file with mode: 0644]

index fbeadbe..1eb65c6 100644 (file)
--- a/README.md
+++ b/README.md
@@ -2,7 +2,20 @@
 
 Poker hand evaluator
 
-This project can be built using wasm-pack.\
+This project can be built using cargo and wasm-bindgen-cli (only some functions are available for wasm).\
 An example is available [here](https://poker-eval.beaudet.xyz).
 
+
+To build the web version locally, you should run
+
+```sh
+cargo build --lib --release --target=wasm32-unknown-unknown
+wasm-bindgen --target web --no-typescript --out-dir static target/wasm32-unknown-unknown/release/poker_eval.wasm
+miniserve static/ # or any other webserver you use...
+```
+
+The released binary is light so I included it in the `static folder`.
+
+A deployment script is available in the `static` folder. You should use it as a post-receive git hook script on the server and set it as an executable.
+
 Based on Kevin L. Suffecool [poker hand evaluator](http://suffe.cool/poker/evaluator.html)
diff --git a/static/deploy.sh b/static/deploy.sh
new file mode 100644 (file)
index 0000000..d64c987
--- /dev/null
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+GIT_REPO="/srv/git/poker-eval.git"
+TMP_GIT_CLONE="/tmp/poker-eval"
+PROD_WWW="/srv/www/poker-eval"
+STAGING_WWW="/srv/www/poker-eval-staging"
+
+while read -r _ _ refname
+do
+       branch=$(git rev-parse --symbolic --abbrev-ref "${refname}")
+
+       if [ "$branch" = "main" ]; then
+               git clone -b main "$GIT_REPO" "$TMP_GIT_CLONE"
+               cp -r "$TMP_GIT_CLONE/static/." "$PROD_WWW"
+               cargo build --manifest-path="$TMP_GIT_CLONE/Cargo.toml" --release --target=wasm32-unknown-unknown
+               wasm-bindgen --target web --no-typescript --out-dir "$PROD_WWW" "$TMP_GIT_CLONE/target/wasm32-unknown-unknown/release/poker_eval.wasm"
+       elif [ "$branch" = "staging" ]; then
+               git clone -b staging "$GIT_REPO" "$TMP_GIT_CLONE"
+               cp -r "$TMP_GIT_CLONE/static/." "$STAGING_WWW"
+               cargo build --manifest-path="$TMP_GIT_CLONE/Cargo.toml" --release --target=wasm32-unknown-unknown
+               wasm-bindgen --target web --no-typescript --out-dir "$STAGING_WWW" "$TMP_GIT_CLONE/target/wasm32-unknown-unknown/release/poker_eval.wasm"
+       fi
+
+       rm -rf "$TMP_GIT_CLONE"
+done
diff --git a/static/favicon.ico b/static/favicon.ico
new file mode 100644 (file)
index 0000000..b8ad237
Binary files /dev/null and b/static/favicon.ico differ
diff --git a/static/index.html b/static/index.html
new file mode 100644 (file)
index 0000000..a9abd05
--- /dev/null
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Poker hand evaluator</title>
+    <link rel="stylesheet" href="styles.css" />
+    <link rel="icon" href="favicon.ico" type="image/x-icon">
+  </head>
+  <body>
+    <noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
+    <h1>Poker hand evaluator</h1>
+    <p>Based on Kevin L. Suffecool <a href="http://suffe.cool/poker/evaluator.html">poker hand evaluator</a>.</p>
+    <p>Returns the hand's equivalence value.
+    The evaluator orders hands from 1 to 7462.
+    The lower the score is, the better it is.
+    </p>
+    <p>You should pass five-hand or seven-hand poker cards.<br />
+    A card contains one of '23456789TJQKA' followed by one of 'cdhs'. This is case-sensitive.</p>
+
+    <label for="eval-input">Cards</label>
+    <input class="input" id="eval-input" placeholder="2s 3d Td Kc 2d" type="text" />
+    <button class="input" id="eval-button" type="submit">Evaluate</button>
+    <div id="result">Score:<br />Hand value:</div>
+    <footer>Code available on my <a href="https://git.beaudet.xyz/?p=poker-eval.git">git repository</a>
+      <script src="index.js" type="module" defer></script>
+  </body>
+</html>
diff --git a/static/index.js b/static/index.js
new file mode 100644 (file)
index 0000000..c6ebd3f
--- /dev/null
@@ -0,0 +1,61 @@
+import init, { eval_from_str, hand_rank } from './poker_eval.js';
+async function run() {
+  await init();
+}
+run();
+
+const evalButton = document.getElementById("eval-button");
+evalButton.addEventListener("click", _ => {
+  const inputText = document.getElementById("eval-input").value;
+  const resultDiv = document.getElementById("result");
+
+  const result = eval_from_str(inputText);
+  const rank = hand_rank(result);
+  resultDiv.innerHTML = "Score: " + result + "<br />";
+  resultDiv.innerHTML += "Hand value: " + VALUE_STR[rank];
+});
+
+const VALUE_STR = [
+  "",
+  "Straight Flush",
+  "Four of a Kind",
+  "Full House",
+  "Flush",
+  "Staight",
+  "Three of a Kind",
+  "Two Pair",
+  "One Pair",
+  "High Card",
+];
+
+// import { eval_from_str, hand_rank } from "poker-eval";
+
+// const evalButton = document.getElementById("eval-button");
+// // const evalForm = document.getElementById("eval-form");
+// // evalForm.addEventListener("click", event => {
+// //  event.preventDefault();
+// // })
+
+// evalButton.addEventListener("click", _ => {
+//   const inputText = document.getElementById("eval-input").value;
+//   const resultDiv = document.getElementById("result");
+
+//   const result = eval_from_str(inputText);
+//   const rank = hand_rank(result);
+//   resultDiv.innerHTML = "Score: " + result + "<br />";
+//   resultDiv.innerHTML += "Hand value: " + VALUE_STR[rank];
+// });
+
+// const VALUE_STR = [
+//     "",
+//     "Straight Flush",
+//     "Four of a Kind",
+//     "Full House",
+//     "Flush",
+//     "Staight",
+//     "Three of a Kind",
+//     "Two Pair",
+//     "One Pair",
+//     "High Card",
+// ];
+
diff --git a/static/poker_eval.js b/static/poker_eval.js
new file mode 100644 (file)
index 0000000..e3c245f
--- /dev/null
@@ -0,0 +1,162 @@
+
+let wasm;
+
+let WASM_VECTOR_LEN = 0;
+
+let cachegetUint8Memory0 = null;
+function getUint8Memory0() {
+    if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) {
+        cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
+    }
+    return cachegetUint8Memory0;
+}
+
+let cachedTextEncoder = new TextEncoder('utf-8');
+
+const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
+    ? function (arg, view) {
+    return cachedTextEncoder.encodeInto(arg, view);
+}
+    : function (arg, view) {
+    const buf = cachedTextEncoder.encode(arg);
+    view.set(buf);
+    return {
+        read: arg.length,
+        written: buf.length
+    };
+});
+
+function passStringToWasm0(arg, malloc, realloc) {
+
+    if (realloc === undefined) {
+        const buf = cachedTextEncoder.encode(arg);
+        const ptr = malloc(buf.length);
+        getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
+        WASM_VECTOR_LEN = buf.length;
+        return ptr;
+    }
+
+    let len = arg.length;
+    let ptr = malloc(len);
+
+    const mem = getUint8Memory0();
+
+    let offset = 0;
+
+    for (; offset < len; offset++) {
+        const code = arg.charCodeAt(offset);
+        if (code > 0x7F) break;
+        mem[ptr + offset] = code;
+    }
+
+    if (offset !== len) {
+        if (offset !== 0) {
+            arg = arg.slice(offset);
+        }
+        ptr = realloc(ptr, len, len = offset + arg.length * 3);
+        const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
+        const ret = encodeString(arg, view);
+
+        offset += ret.written;
+    }
+
+    WASM_VECTOR_LEN = offset;
+    return ptr;
+}
+/**
+* Wrapper over both five-card and seven-card hand evaluator from string.
+*
+* The evaluator orders hands from 1 to 7462.
+*
+* # Example
+*
+* ```
+* use poker_eval::{eval_from_str};
+*
+* assert_eq!(eval_from_str("4s 4d 4h 4c 5d"), 140);
+* assert_eq!(eval_from_str("Ts 9s 8s 7s 6s 7h 6d"), 5);
+* ```
+* @param {string} s
+* @returns {number}
+*/
+export function eval_from_str(s) {
+    var ptr0 = passStringToWasm0(s, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+    var len0 = WASM_VECTOR_LEN;
+    var ret = wasm.eval_from_str(ptr0, len0);
+    return ret >>> 0;
+}
+
+/**
+* Returns the hand category based on the hand evaluation.
+*
+* # Example
+*
+* ```
+* use poker_eval::{eval_from_str, hand_rank, constants::FLUSH};
+*
+* let eval = eval_from_str("Js 9s 7s 3s 2s");
+* assert_eq!(eval, 1433);
+* assert_eq!(hand_rank(eval), FLUSH);
+* ```
+* @param {number} i
+* @returns {number}
+*/
+export function hand_rank(i) {
+    var ret = wasm.hand_rank(i);
+    return ret >>> 0;
+}
+
+async function load(module, imports) {
+    if (typeof Response === 'function' && module instanceof Response) {
+        if (typeof WebAssembly.instantiateStreaming === 'function') {
+            try {
+                return await WebAssembly.instantiateStreaming(module, imports);
+
+            } catch (e) {
+                if (module.headers.get('Content-Type') != 'application/wasm') {
+                    console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
+
+                } else {
+                    throw e;
+                }
+            }
+        }
+
+        const bytes = await module.arrayBuffer();
+        return await WebAssembly.instantiate(bytes, imports);
+
+    } else {
+        const instance = await WebAssembly.instantiate(module, imports);
+
+        if (instance instanceof WebAssembly.Instance) {
+            return { instance, module };
+
+        } else {
+            return instance;
+        }
+    }
+}
+
+async function init(input) {
+    if (typeof input === 'undefined') {
+        input = new URL('poker_eval_bg.wasm', import.meta.url);
+    }
+    const imports = {};
+
+
+    if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
+        input = fetch(input);
+    }
+
+
+
+    const { instance, module } = await load(await input, imports);
+
+    wasm = instance.exports;
+    init.__wbindgen_wasm_module = module;
+
+    return wasm;
+}
+
+export default init;
+
diff --git a/static/poker_eval_bg.wasm b/static/poker_eval_bg.wasm
new file mode 100644 (file)
index 0000000..dae367c
Binary files /dev/null and b/static/poker_eval_bg.wasm differ
diff --git a/static/styles.css b/static/styles.css
new file mode 100644 (file)
index 0000000..fa81fe6
--- /dev/null
@@ -0,0 +1,62 @@
+/* Adapted from https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/ */
+*,
+*::before,
+*::after {
+  box-sizing: border-box;
+}
+
+:root {
+  --input-border: #8b8a8b;
+  --input-focus-h: 245;
+  --input-focus-s: 100%;
+  --input-focus-l: 42%;
+}
+
+body {
+  min-height: 100vh;
+  display: grid;
+  place-content: center;
+  gap: 0.5rem;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+  background-color: #e9f2fd;
+  padding: 1rem;
+}
+
+label {
+  font-size: 1.125rem;
+  font-weight: 500;
+  line-height: 1;
+}
+
+.input + label {
+  margin-top: 2rem;
+}
+
+.input {
+  font-size: 16px;
+  font-size: max(16px, 1em);
+  font-family: inherit;
+  padding: 0.25em 0.5em;
+  background-color: #fff;
+  border: 2px solid var(--input-border);
+  border-radius: 4px;
+  transition: 180ms box-shadow ease-in-out;
+  line-height: 1;
+  height: 2.25rem;
+}
+
+.input:focus {
+  border-color: hsl(
+    var(--input-focus-h),
+    var(--input-focus-s),
+    var(--input-focus-l)
+  );
+  box-shadow: 0 0 0 3px
+    hsla(
+      var(--input-focus-h),
+      var(--input-focus-s),
+      calc(var(--input-focus-l) + 40%),
+      0.8
+    );
+  outline: 3px solid transparent;
+}