@imperialbower/bowery 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MANIFEST.sha256 +8 -0
- package/README.md +49 -5
- package/bowery.css +73 -0
- package/bowery.js +413 -0
- package/bowery_wasm.js +609 -0
- package/bowery_wasm_bg.wasm +0 -0
- package/package.json +26 -4
package/MANIFEST.sha256
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
775ebac50d3042c8c90b52418414588ea1b7b433fbc854899e908b6f8ec92158 LICENSE-APACHE
|
|
2
|
+
a5fb933e5d0221688f909047fbe29b70b11cb25d5cb9a08e59a269df7ee15266 LICENSE-MIT
|
|
3
|
+
d15cfb86c381c17f40bcae4977098e77aa74caf1cad1f49cf409c1c89e3702cd README.md
|
|
4
|
+
a9224c7609958d7222e9dcf3718020873e05c56535c24e09c21a71587f2170ae bowery.css
|
|
5
|
+
69b0d329338f15d473a4fb3920863948dc8f92fba01857882dade8752ebc5eec bowery.js
|
|
6
|
+
47cd091c2d631f544ba4c9c6ae902b338773dceec3deb42571f40b19b32acf80 bowery_wasm.js
|
|
7
|
+
b8412512d47cb9fdbbeb8c434d0e93d5b69b65f851eb6c91318c2dca90f68c03 bowery_wasm_bg.wasm
|
|
8
|
+
39b49a99faf77e9dde1703dfad704b28c90f88587dd0e6d7ae6197676759cbb5 package.json
|
package/README.md
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
[](https://crates.io/crates/bowery)
|
|
2
|
+
[](https://www.npmjs.com/package/@imperialbower/bowery)
|
|
2
3
|
[](https://github.com/ImperialBower/bowery/actions/workflows/ci.yaml)
|
|
3
4
|
[](CODE_OF_CONDUCT.md)
|
|
4
5
|
[](LICENSE-MIT)
|
|
@@ -20,18 +21,61 @@ crate builds two ways: an rlib kernel (config, check, game, frame, SVG
|
|
|
20
21
|
drawing) and, behind a `wasm` feature, the browser element. The design is
|
|
21
22
|
bower's [EPIC-20](https://github.com/ImperialBower/bower/blob/main/docs/EPIC-20_Bowery.md).
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
## In a web page
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
```html
|
|
27
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/@imperialbower/bowery@0.1.0/bowery.js"></script>
|
|
28
|
+
|
|
29
|
+
<bowery-table>
|
|
30
|
+
<script type="application/yaml">
|
|
31
|
+
mode: play
|
|
32
|
+
seat: 0
|
|
33
|
+
table: { stakes: { small_blind: 50.0, big_blind: 100.0 } }
|
|
34
|
+
players:
|
|
35
|
+
- { seat: 0, name: You, stack: 10000 }
|
|
36
|
+
- { seat: 1, stack: 10000, bot: tight_aggressive }
|
|
37
|
+
- { seat: 2, stack: 10000, bot: maniac }
|
|
38
|
+
</script>
|
|
39
|
+
<p>A poker table: it needs JavaScript.</p> <!-- the fallback, kept until the table is ready -->
|
|
40
|
+
</bowery-table>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The element loads its wasm from beside `bowery.js` (or from its `base`
|
|
44
|
+
attribute) and asks nothing of any other origin. To serve it yourself, take
|
|
45
|
+
`bowery-vX.Y.Z.tar.gz` from a [release](https://github.com/ImperialBower/bowery/releases):
|
|
46
|
+
one flat directory whose files `MANIFEST.sha256` lists. A config can also
|
|
47
|
+
come from a file (`src="table.yaml"`), and `mode: replay` or
|
|
48
|
+
`mode: interactive` take a recorded pkcore hand history as `hand:`. The
|
|
49
|
+
config's fields are documented on [`Config`](https://docs.rs/bowery/latest/bowery/config/struct.Config.html).
|
|
50
|
+
|
|
51
|
+
## In Rust
|
|
52
|
+
|
|
53
|
+
```rust
|
|
54
|
+
use bowery::check::check;
|
|
55
|
+
use bowery::config::{Config, View};
|
|
56
|
+
use bowery::draw::{Theme, svg};
|
|
57
|
+
use bowery::game::Game;
|
|
58
|
+
|
|
59
|
+
let config = Config::from_yaml(yaml)?;
|
|
60
|
+
let game = Game::new(check(&config).map_err(|problems| problems[0].clone())?)?;
|
|
61
|
+
let picture = svg(&game.frame(View::All), config.layout, 680, Theme::Light);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`cargo run --example bowery-still -- fixtures/the_hand.yaml --layout spotlight --at river`
|
|
65
|
+
prints The Hand on the river as one SVG.
|
|
66
|
+
|
|
67
|
+
## Develop
|
|
26
68
|
|
|
27
69
|
```sh
|
|
28
70
|
git clone https://github.com/ImperialBower/bowery.git
|
|
29
71
|
cd bowery
|
|
30
|
-
make install-tools # cargo-deny, cargo-audit, cargo-watch
|
|
31
|
-
make ayce # the full gate
|
|
72
|
+
make install-tools # the wasm target, cargo-deny, cargo-audit, cargo-watch
|
|
73
|
+
make ayce # the full gate, the wasm size gate and the Playwright proof included
|
|
74
|
+
make serve # the demo at http://localhost:8000
|
|
32
75
|
```
|
|
33
76
|
|
|
34
|
-
`make
|
|
77
|
+
`make ayce` also needs `wasm-pack` and Node (see `.tool-versions`). `make help`
|
|
78
|
+
lists every target.
|
|
35
79
|
|
|
36
80
|
## What's in the box
|
|
37
81
|
|
package/bowery.css
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/* <bowery-table>: loaded into the element's shadow root, so nothing here
|
|
2
|
+
reaches the page. Black on white, 2 px strokes, the system monospace;
|
|
3
|
+
:host([data-dark]) when the page is dark. No fonts, no url(). */
|
|
4
|
+
:host {
|
|
5
|
+
--bowery-bg: #ffffff;
|
|
6
|
+
--bowery-fg: #000000;
|
|
7
|
+
--bowery-to-act: #ffe94d;
|
|
8
|
+
display: block;
|
|
9
|
+
max-width: 100%;
|
|
10
|
+
border: 2px solid var(--bowery-fg);
|
|
11
|
+
background: var(--bowery-bg);
|
|
12
|
+
color: var(--bowery-fg);
|
|
13
|
+
font-family: ui-monospace, Menlo, Consolas, monospace;
|
|
14
|
+
font-size: 13px;
|
|
15
|
+
}
|
|
16
|
+
:host([data-dark]) {
|
|
17
|
+
--bowery-bg: #111111;
|
|
18
|
+
--bowery-fg: #f2f2f2;
|
|
19
|
+
--bowery-to-act: #5e5200;
|
|
20
|
+
}
|
|
21
|
+
:host(:focus-visible) {
|
|
22
|
+
outline: 3px solid var(--bowery-to-act);
|
|
23
|
+
outline-offset: 2px;
|
|
24
|
+
}
|
|
25
|
+
.stage svg {
|
|
26
|
+
display: block;
|
|
27
|
+
max-width: 100%;
|
|
28
|
+
height: auto;
|
|
29
|
+
}
|
|
30
|
+
.bar {
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-wrap: wrap;
|
|
33
|
+
gap: 6px;
|
|
34
|
+
align-items: center;
|
|
35
|
+
border-top: 2px solid var(--bowery-fg);
|
|
36
|
+
padding: 6px 8px;
|
|
37
|
+
}
|
|
38
|
+
.bar:empty {
|
|
39
|
+
display: none;
|
|
40
|
+
}
|
|
41
|
+
button,
|
|
42
|
+
select,
|
|
43
|
+
input {
|
|
44
|
+
font: inherit;
|
|
45
|
+
color: var(--bowery-fg);
|
|
46
|
+
background: var(--bowery-bg);
|
|
47
|
+
border: 2px solid var(--bowery-fg);
|
|
48
|
+
border-radius: 0;
|
|
49
|
+
padding: 2px 8px;
|
|
50
|
+
}
|
|
51
|
+
button {
|
|
52
|
+
font-weight: 700;
|
|
53
|
+
cursor: pointer;
|
|
54
|
+
}
|
|
55
|
+
button:hover,
|
|
56
|
+
button:focus-visible {
|
|
57
|
+
background: var(--bowery-fg);
|
|
58
|
+
color: var(--bowery-bg);
|
|
59
|
+
}
|
|
60
|
+
input[type='number'] {
|
|
61
|
+
width: 9ch;
|
|
62
|
+
}
|
|
63
|
+
.readout {
|
|
64
|
+
flex: 1;
|
|
65
|
+
min-width: 8ch;
|
|
66
|
+
}
|
|
67
|
+
.status {
|
|
68
|
+
position: absolute;
|
|
69
|
+
width: 1px;
|
|
70
|
+
height: 1px;
|
|
71
|
+
overflow: hidden;
|
|
72
|
+
clip-path: inset(50%);
|
|
73
|
+
}
|
package/bowery.js
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
// <bowery-table>: one poker table control, configured not compiled.
|
|
2
|
+
//
|
|
3
|
+
// Usage:
|
|
4
|
+
// <script type="module" src="bowery/bowery.js"></script>
|
|
5
|
+
// <bowery-table base="bowery/">
|
|
6
|
+
// <script type="application/yaml">mode: replay …</script>
|
|
7
|
+
// <svg …>the fallback picture</svg>
|
|
8
|
+
// </bowery-table>
|
|
9
|
+
//
|
|
10
|
+
// Nothing loads until the element is within a screen of the viewport. The
|
|
11
|
+
// first element instantiates the wasm module; every later one reuses it and
|
|
12
|
+
// builds its own BoweryTable, so tables on a page are independent. The
|
|
13
|
+
// fallback picture stays if the config, the import or the init fails, and a
|
|
14
|
+
// config's problems are printed under it. Every request goes to `base`
|
|
15
|
+
// (default: this module's directory) or to `src`, both on the page's origin.
|
|
16
|
+
// Contract: bower docs/EPIC-20_Bowery.md § <bowery-table>.
|
|
17
|
+
|
|
18
|
+
const HERE = new URL('.', import.meta.url);
|
|
19
|
+
const GLUE = 'bowery_wasm.js';
|
|
20
|
+
const WASM = 'bowery_wasm_bg.wasm';
|
|
21
|
+
const STYLE = 'bowery.css';
|
|
22
|
+
|
|
23
|
+
/** One import and one init per base, shared by every element. */
|
|
24
|
+
const modules = new Map();
|
|
25
|
+
|
|
26
|
+
function loadModule(base) {
|
|
27
|
+
const key = base.href;
|
|
28
|
+
if (!modules.has(key)) {
|
|
29
|
+
modules.set(
|
|
30
|
+
key,
|
|
31
|
+
import(new URL(GLUE, base).href).then(async (glue) => {
|
|
32
|
+
await glue.default({ module_or_path: new URL(WASM, base) });
|
|
33
|
+
return glue;
|
|
34
|
+
}),
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return modules.get(key);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** mdBook's dark themes, or the system's dark preference. */
|
|
41
|
+
function prefersDark() {
|
|
42
|
+
const html = document.documentElement.classList;
|
|
43
|
+
if (html.contains('coal') || html.contains('navy') || html.contains('ayu')) return true;
|
|
44
|
+
if (html.contains('light') || html.contains('rust')) return false;
|
|
45
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** A button; `name` is its accessible name when the label is a symbol. */
|
|
49
|
+
function button(label, onClick, name) {
|
|
50
|
+
const element = document.createElement('button');
|
|
51
|
+
element.type = 'button';
|
|
52
|
+
element.textContent = label;
|
|
53
|
+
if (name) {
|
|
54
|
+
element.setAttribute('aria-label', name);
|
|
55
|
+
element.title = name;
|
|
56
|
+
}
|
|
57
|
+
element.addEventListener('click', onClick);
|
|
58
|
+
return element;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class BoweryTableElement extends HTMLElement {
|
|
62
|
+
static get observedAttributes() {
|
|
63
|
+
return ['src'];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
connectedCallback() {
|
|
67
|
+
if (this._wired) return;
|
|
68
|
+
this._wired = true;
|
|
69
|
+
this._base = new URL(this.getAttribute('base') || HERE.href, document.baseURI);
|
|
70
|
+
if (!this.hasAttribute('tabindex')) this.tabIndex = 0;
|
|
71
|
+
const observer = new IntersectionObserver(
|
|
72
|
+
(entries) => {
|
|
73
|
+
if (entries.some((entry) => entry.isIntersecting)) {
|
|
74
|
+
observer.disconnect();
|
|
75
|
+
this._start();
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
{ rootMargin: '100% 0px' },
|
|
79
|
+
);
|
|
80
|
+
observer.observe(this);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
disconnectedCallback() {
|
|
84
|
+
clearTimeout(this._timer);
|
|
85
|
+
this._resize?.disconnect();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
attributeChangedCallback(name, before, after) {
|
|
89
|
+
if (name === 'src' && this._table && before !== after) this._reload();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async _reload() {
|
|
93
|
+
clearTimeout(this._timer);
|
|
94
|
+
this._table?.free?.();
|
|
95
|
+
this._table = null;
|
|
96
|
+
await this._start();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async _start() {
|
|
100
|
+
if (location.protocol === 'file:') {
|
|
101
|
+
this._fail('this table needs a served page');
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
let yaml;
|
|
105
|
+
try {
|
|
106
|
+
yaml = await this._config();
|
|
107
|
+
} catch (error) {
|
|
108
|
+
this._fail(`the config did not load: ${error.message}`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
let glue;
|
|
112
|
+
try {
|
|
113
|
+
glue = await loadModule(this._base);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
this._fail('the table did not load');
|
|
116
|
+
this._emit('bowery:error', { message: String(error) });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
this._table = new glue.BoweryTable(yaml);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
this._problems(String(error));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
this._version = glue.BoweryTable.version();
|
|
126
|
+
this._mount();
|
|
127
|
+
this._render();
|
|
128
|
+
this._emit('bowery:ready', { version: this._version });
|
|
129
|
+
this._pace();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** The inline YAML, or the file `src` names (same origin only). */
|
|
133
|
+
async _config() {
|
|
134
|
+
const src = this.getAttribute('src');
|
|
135
|
+
if (src) {
|
|
136
|
+
const url = new URL(src, document.baseURI);
|
|
137
|
+
if (url.origin !== location.origin) throw new Error('src must be on this page\'s origin');
|
|
138
|
+
const response = await fetch(url);
|
|
139
|
+
if (!response.ok) throw new Error(`${response.status} ${response.statusText}`);
|
|
140
|
+
return response.text();
|
|
141
|
+
}
|
|
142
|
+
const script = this.querySelector('script[type="application/yaml"]');
|
|
143
|
+
if (!script) throw new Error('no <script type="application/yaml"> and no src');
|
|
144
|
+
return script.textContent;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** The fallback stays; a note goes under it. */
|
|
148
|
+
_fail(message) {
|
|
149
|
+
const note = document.createElement('p');
|
|
150
|
+
note.className = 'bowery-note';
|
|
151
|
+
note.textContent = message;
|
|
152
|
+
this.append(note);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** The fallback stays; the config's problems go under it. */
|
|
156
|
+
_problems(json) {
|
|
157
|
+
let lines;
|
|
158
|
+
try {
|
|
159
|
+
lines = JSON.parse(json).map((problem) => (problem.path ? `${problem.path}: ${problem.message}` : problem.message));
|
|
160
|
+
} catch {
|
|
161
|
+
lines = [json];
|
|
162
|
+
}
|
|
163
|
+
const pre = document.createElement('pre');
|
|
164
|
+
pre.className = 'bowery-problems';
|
|
165
|
+
pre.textContent = lines.join('\n');
|
|
166
|
+
this.append(pre);
|
|
167
|
+
this._emit('bowery:error', { problems: lines });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
_mount() {
|
|
171
|
+
const root = this.shadowRoot || this.attachShadow({ mode: 'open' });
|
|
172
|
+
root.replaceChildren();
|
|
173
|
+
// A custom element is inline until bowery.css arrives; block at once, so
|
|
174
|
+
// the first frame is measured and drawn at the element's real width.
|
|
175
|
+
const block = document.createElement('style');
|
|
176
|
+
block.textContent = ':host { display: block; }';
|
|
177
|
+
const style = document.createElement('link');
|
|
178
|
+
style.rel = 'stylesheet';
|
|
179
|
+
style.href = new URL(STYLE, this._base).href;
|
|
180
|
+
this._stage = document.createElement('div');
|
|
181
|
+
this._stage.className = 'stage';
|
|
182
|
+
this._bar = document.createElement('div');
|
|
183
|
+
this._bar.className = 'bar';
|
|
184
|
+
this._status = document.createElement('div');
|
|
185
|
+
this._status.className = 'status';
|
|
186
|
+
this._status.setAttribute('aria-live', 'polite');
|
|
187
|
+
root.append(block, style, this._stage, this._bar, this._status);
|
|
188
|
+
|
|
189
|
+
this._width = this.clientWidth || 680;
|
|
190
|
+
this._resize = new ResizeObserver((entries) => {
|
|
191
|
+
const width = Math.round(entries[0].contentRect.width);
|
|
192
|
+
if (width > 0 && width !== this._width) {
|
|
193
|
+
this._width = width;
|
|
194
|
+
this._draw();
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
this._resize.observe(this);
|
|
198
|
+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => this._render());
|
|
199
|
+
new MutationObserver(() => this._render()).observe(document.documentElement, {
|
|
200
|
+
attributes: true,
|
|
201
|
+
attributeFilter: ['class'],
|
|
202
|
+
});
|
|
203
|
+
this.addEventListener('keydown', (event) => this._key(event));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
_state() {
|
|
207
|
+
return JSON.parse(this._table.state());
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
_draw() {
|
|
211
|
+
const dark = prefersDark();
|
|
212
|
+
this.toggleAttribute('data-dark', dark);
|
|
213
|
+
// The SVG is bowery's own renderer's: every name and caption from the
|
|
214
|
+
// config is XML-escaped there (tests/draw.rs draw__names_are_escaped),
|
|
215
|
+
// and it holds no script.
|
|
216
|
+
this._stage.innerHTML = this._table.svg(this._width, dark);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
_render() {
|
|
220
|
+
if (!this._table) return;
|
|
221
|
+
const before = this._last;
|
|
222
|
+
const state = this._state();
|
|
223
|
+
this._last = state;
|
|
224
|
+
this._draw();
|
|
225
|
+
this._controls(state);
|
|
226
|
+
this._status.textContent = state.caption;
|
|
227
|
+
this._emit('bowery:moment', { moment: state.moment - 1, moments: state.moments });
|
|
228
|
+
if (state.record === 'off' && before?.record !== 'off') this._emit('bowery:off-record', { since: state.since });
|
|
229
|
+
if (state.hand_over && !before?.hand_over) this._emit('bowery:hand-over', { result: state.result });
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** The bar under the table, rebuilt for the state; focus stays put. */
|
|
233
|
+
_controls(state) {
|
|
234
|
+
const focused = this.shadowRoot.activeElement?.dataset?.control;
|
|
235
|
+
const bar = this._bar;
|
|
236
|
+
bar.replaceChildren();
|
|
237
|
+
const add = (element, control) => {
|
|
238
|
+
element.dataset.control = control;
|
|
239
|
+
bar.append(element);
|
|
240
|
+
return element;
|
|
241
|
+
};
|
|
242
|
+
if (state.controls.seek) {
|
|
243
|
+
add(button('⏮', () => this._seek(0), 'first'), 'first');
|
|
244
|
+
add(button('◀', () => this._seek(state.moment - 2), 'back'), 'back');
|
|
245
|
+
add(button('▶', () => this._step(), 'next'), 'next');
|
|
246
|
+
add(button('⏭', () => this._seek(state.moments - 1), 'last'), 'last');
|
|
247
|
+
const readout = document.createElement('span');
|
|
248
|
+
readout.className = 'readout';
|
|
249
|
+
readout.textContent = `${state.moment} / ${state.moments}`;
|
|
250
|
+
add(readout, 'readout');
|
|
251
|
+
}
|
|
252
|
+
if (state.controls.take_seat && !state.hand_over) {
|
|
253
|
+
const select = document.createElement('select');
|
|
254
|
+
select.setAttribute('aria-label', 'seat');
|
|
255
|
+
for (const seat of state.seats.filter((seat) => seat.live)) {
|
|
256
|
+
const option = document.createElement('option');
|
|
257
|
+
option.value = seat.seat;
|
|
258
|
+
option.textContent = seat.name;
|
|
259
|
+
option.selected = seat.seat === state.reader;
|
|
260
|
+
select.append(option);
|
|
261
|
+
}
|
|
262
|
+
add(select, 'seat');
|
|
263
|
+
add(button('Take this seat', () => this._take(Number(select.value))), 'take');
|
|
264
|
+
}
|
|
265
|
+
if (state.controls.back_to_record) add(button('Back to the record', () => this._back()), 'back-to-record');
|
|
266
|
+
// Empty unless the reader can act at this moment.
|
|
267
|
+
if (state.legal.length > 0) this._actions(state, add);
|
|
268
|
+
if (state.controls.next_hand) add(button('Next hand', () => this._next()), 'next-hand');
|
|
269
|
+
if (state.controls.shuffle) add(button('Shuffle', () => this._shuffle()), 'shuffle');
|
|
270
|
+
if (state.controls.view) {
|
|
271
|
+
add(button(`👁 ${state.view}`, () => this._toggleView(state.view)), 'view');
|
|
272
|
+
}
|
|
273
|
+
if (state.controls.layout) {
|
|
274
|
+
add(button(`▦ ${state.layout}`, () => this._toggleLayout(state.layout)), 'layout');
|
|
275
|
+
}
|
|
276
|
+
const again = focused && bar.querySelector(`[data-control="${focused}"]`);
|
|
277
|
+
if (again) again.focus();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** The reader's legal actions as buttons; a size box for bet or raise. */
|
|
281
|
+
_actions(state, add) {
|
|
282
|
+
for (const legal of state.legal) {
|
|
283
|
+
if (legal.action === 'bet' || legal.action === 'raise') {
|
|
284
|
+
const size = document.createElement('input');
|
|
285
|
+
size.type = 'number';
|
|
286
|
+
size.min = legal.min;
|
|
287
|
+
size.max = legal.max;
|
|
288
|
+
size.value = legal.min;
|
|
289
|
+
size.setAttribute('aria-label', `${legal.action} to`);
|
|
290
|
+
add(size, `${legal.action}-size`);
|
|
291
|
+
add(
|
|
292
|
+
button(legal.action === 'bet' ? 'Bet' : 'Raise to', () =>
|
|
293
|
+
this._act({ action: legal.action, amount: Number(size.value) }),
|
|
294
|
+
),
|
|
295
|
+
legal.action,
|
|
296
|
+
);
|
|
297
|
+
} else {
|
|
298
|
+
const label = {
|
|
299
|
+
fold: 'Fold',
|
|
300
|
+
check: 'Check',
|
|
301
|
+
call: `Call ${legal.amount?.toLocaleString('en-US') ?? ''}`.trim(),
|
|
302
|
+
all_in: 'All in',
|
|
303
|
+
}[legal.action];
|
|
304
|
+
add(button(label, () => this._act({ action: legal.action })), legal.action);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
_key(event) {
|
|
310
|
+
const state = this._last;
|
|
311
|
+
if (!state?.controls.seek || event.target !== this) return;
|
|
312
|
+
const moves = {
|
|
313
|
+
ArrowLeft: state.moment - 2,
|
|
314
|
+
ArrowRight: state.moment,
|
|
315
|
+
Home: 0,
|
|
316
|
+
End: state.moments - 1,
|
|
317
|
+
};
|
|
318
|
+
if (event.key in moves) {
|
|
319
|
+
event.preventDefault();
|
|
320
|
+
this._seek(moves[event.key]);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
_seek(index) {
|
|
325
|
+
clearTimeout(this._timer);
|
|
326
|
+
this._table.seek(Math.max(0, index));
|
|
327
|
+
this._render();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** ▶: one moment on; on the record it stops at the reader's turn. */
|
|
331
|
+
_step() {
|
|
332
|
+
this._try(() => this._table.advance());
|
|
333
|
+
this._pace();
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
_take(seat) {
|
|
337
|
+
this._try(() => this._table.take_seat(seat));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
_back() {
|
|
341
|
+
clearTimeout(this._timer);
|
|
342
|
+
this._table.back_to_record();
|
|
343
|
+
this._render();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
_act(action) {
|
|
347
|
+
this._try(() => this._table.act(JSON.stringify(action)));
|
|
348
|
+
this._pace();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
_next() {
|
|
352
|
+
this._try(() => this._table.next_hand());
|
|
353
|
+
this._pace();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
_shuffle() {
|
|
357
|
+
clearTimeout(this._timer);
|
|
358
|
+
this._try(() => this._table.shuffle((Math.random() * 0xffffffff) >>> 0));
|
|
359
|
+
this._pace();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
_toggleView(view) {
|
|
363
|
+
this._table.set_view(view === 'all' ? 'seat' : 'all');
|
|
364
|
+
this._render();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
_toggleLayout(layout) {
|
|
368
|
+
const order = ['ring', 'spotlight', 'ledger'];
|
|
369
|
+
this._table.set_layout(order[(order.indexOf(layout) + 1) % order.length]);
|
|
370
|
+
this._render();
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
_try(move) {
|
|
374
|
+
try {
|
|
375
|
+
move();
|
|
376
|
+
} catch (error) {
|
|
377
|
+
this._status.textContent = String(error);
|
|
378
|
+
this._emit('bowery:error', { message: String(error) });
|
|
379
|
+
}
|
|
380
|
+
this._render();
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Off the record and in play, bots and deals go on by themselves, one
|
|
385
|
+
* moment per `delay_ms`, while the latest moment is on screen and it is
|
|
386
|
+
* not the reader's turn.
|
|
387
|
+
*/
|
|
388
|
+
_pace() {
|
|
389
|
+
clearTimeout(this._timer);
|
|
390
|
+
const state = this._state();
|
|
391
|
+
const live = state.mode === 'play' || state.record === 'off';
|
|
392
|
+
if (!live || state.hand_over || state.moment !== state.moments) return;
|
|
393
|
+
this._timer = setTimeout(() => {
|
|
394
|
+
let answer;
|
|
395
|
+
try {
|
|
396
|
+
answer = this._table.advance();
|
|
397
|
+
} catch (error) {
|
|
398
|
+
this._emit('bowery:error', { message: String(error) });
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
this._render();
|
|
402
|
+
if (answer === 'acted' || answer === 'dealt') this._pace();
|
|
403
|
+
}, state.delay_ms);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
_emit(name, detail) {
|
|
407
|
+
this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }));
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (!customElements.get('bowery-table')) customElements.define('bowery-table', BoweryTableElement);
|
|
412
|
+
|
|
413
|
+
export { BoweryTableElement };
|
package/bowery_wasm.js
ADDED
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One element's table, for JavaScript.
|
|
3
|
+
*
|
|
4
|
+
* # Examples
|
|
5
|
+
*
|
|
6
|
+
* ```
|
|
7
|
+
* use bowery::wasm::BoweryTable;
|
|
8
|
+
*
|
|
9
|
+
* let table = BoweryTable::new("
|
|
10
|
+
* mode: play
|
|
11
|
+
* table: { stakes: { small_blind: 1.0, big_blind: 2.0 } }
|
|
12
|
+
* players: [ { seat: 0, stack: 200 }, { seat: 1, stack: 200, bot: abc } ]
|
|
13
|
+
* ").unwrap();
|
|
14
|
+
* assert!(table.state().contains(r#""mode":"play""#));
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export class BoweryTable {
|
|
18
|
+
__destroy_into_raw() {
|
|
19
|
+
const ptr = this.__wbg_ptr;
|
|
20
|
+
this.__wbg_ptr = 0;
|
|
21
|
+
BoweryTableFinalization.unregister(this);
|
|
22
|
+
return ptr;
|
|
23
|
+
}
|
|
24
|
+
free() {
|
|
25
|
+
const ptr = this.__destroy_into_raw();
|
|
26
|
+
wasm.__wbg_bowerytable_free(ptr, 0);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The reader acts: `{"action":"raise","amount":600}`.
|
|
30
|
+
*
|
|
31
|
+
* # Errors
|
|
32
|
+
*
|
|
33
|
+
* The JSON does not read, or the game refuses the action.
|
|
34
|
+
*
|
|
35
|
+
* # Examples
|
|
36
|
+
*
|
|
37
|
+
* ```
|
|
38
|
+
* use bowery::wasm::BoweryTable;
|
|
39
|
+
*
|
|
40
|
+
* let mut table = BoweryTable::new("
|
|
41
|
+
* mode: play
|
|
42
|
+
* table: { button: 0, stakes: { small_blind: 1.0, big_blind: 2.0 } }
|
|
43
|
+
* players: [ { seat: 0, stack: 200 }, { seat: 1, stack: 200, bot: abc } ]
|
|
44
|
+
* ").unwrap();
|
|
45
|
+
* assert!(table.act(r#"{"action":"call"}"#).is_ok());
|
|
46
|
+
* ```
|
|
47
|
+
* @param {string} action_json
|
|
48
|
+
*/
|
|
49
|
+
act(action_json) {
|
|
50
|
+
const ptr0 = passStringToWasm0(action_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
51
|
+
const len0 = WASM_VECTOR_LEN;
|
|
52
|
+
const ret = wasm.bowerytable_act(this.__wbg_ptr, ptr0, len0);
|
|
53
|
+
if (ret[1]) {
|
|
54
|
+
throw takeFromExternrefTable0(ret[0]);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* One step: `"acted"`, `"dealt"`, `"waiting"` or `"over"`.
|
|
59
|
+
*
|
|
60
|
+
* # Errors
|
|
61
|
+
*
|
|
62
|
+
* The engine failed.
|
|
63
|
+
*
|
|
64
|
+
* # Examples
|
|
65
|
+
*
|
|
66
|
+
* ```
|
|
67
|
+
* use bowery::wasm::BoweryTable;
|
|
68
|
+
*
|
|
69
|
+
* let mut table = BoweryTable::new("
|
|
70
|
+
* mode: play
|
|
71
|
+
* table: { button: 0, stakes: { small_blind: 1.0, big_blind: 2.0 } }
|
|
72
|
+
* players: [ { seat: 0, stack: 200 }, { seat: 1, stack: 200, bot: abc } ]
|
|
73
|
+
* ").unwrap();
|
|
74
|
+
* assert_eq!("waiting", table.advance().unwrap());
|
|
75
|
+
* ```
|
|
76
|
+
* @returns {string}
|
|
77
|
+
*/
|
|
78
|
+
advance() {
|
|
79
|
+
let deferred2_0;
|
|
80
|
+
let deferred2_1;
|
|
81
|
+
try {
|
|
82
|
+
const ret = wasm.bowerytable_advance(this.__wbg_ptr);
|
|
83
|
+
var ptr1 = ret[0];
|
|
84
|
+
var len1 = ret[1];
|
|
85
|
+
if (ret[3]) {
|
|
86
|
+
ptr1 = 0; len1 = 0;
|
|
87
|
+
throw takeFromExternrefTable0(ret[2]);
|
|
88
|
+
}
|
|
89
|
+
deferred2_0 = ptr1;
|
|
90
|
+
deferred2_1 = len1;
|
|
91
|
+
return getStringFromWasm0(ptr1, len1);
|
|
92
|
+
} finally {
|
|
93
|
+
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Back to the record at the takeover moment.
|
|
98
|
+
*
|
|
99
|
+
* # Examples
|
|
100
|
+
*
|
|
101
|
+
* ```
|
|
102
|
+
* use bowery::wasm::BoweryTable;
|
|
103
|
+
*
|
|
104
|
+
* let mut table = BoweryTable::new("
|
|
105
|
+
* mode: play
|
|
106
|
+
* table: { stakes: { small_blind: 1.0, big_blind: 2.0 } }
|
|
107
|
+
* players: [ { seat: 0, stack: 200 }, { seat: 1, stack: 200, bot: abc } ]
|
|
108
|
+
* ").unwrap();
|
|
109
|
+
* table.back_to_record();
|
|
110
|
+
* assert!(table.state().contains(r#""record":"none""#));
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
back_to_record() {
|
|
114
|
+
wasm.bowerytable_back_to_record(this.__wbg_ptr);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Reads, checks and builds a table from a YAML config.
|
|
118
|
+
*
|
|
119
|
+
* # Errors
|
|
120
|
+
*
|
|
121
|
+
* The config's problems as a JSON string (see
|
|
122
|
+
* [`crate::element::problems_json`]).
|
|
123
|
+
*
|
|
124
|
+
* # Examples
|
|
125
|
+
*
|
|
126
|
+
* ```
|
|
127
|
+
* use bowery::wasm::BoweryTable;
|
|
128
|
+
*
|
|
129
|
+
* let table = BoweryTable::new("
|
|
130
|
+
* mode: play
|
|
131
|
+
* table: { stakes: { small_blind: 1.0, big_blind: 2.0 } }
|
|
132
|
+
* players: [ { seat: 0, stack: 200 }, { seat: 1, stack: 200, bot: abc } ]
|
|
133
|
+
* ");
|
|
134
|
+
* assert!(table.is_ok());
|
|
135
|
+
* ```
|
|
136
|
+
* @param {string} config_yaml
|
|
137
|
+
*/
|
|
138
|
+
constructor(config_yaml) {
|
|
139
|
+
const ptr0 = passStringToWasm0(config_yaml, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
140
|
+
const len0 = WASM_VECTOR_LEN;
|
|
141
|
+
const ret = wasm.bowerytable_new(ptr0, len0);
|
|
142
|
+
if (ret[2]) {
|
|
143
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
144
|
+
}
|
|
145
|
+
this.__wbg_ptr = ret[0];
|
|
146
|
+
BoweryTableFinalization.register(this, this.__wbg_ptr, this);
|
|
147
|
+
return this;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Play: deals the next hand.
|
|
151
|
+
*
|
|
152
|
+
* # Errors
|
|
153
|
+
*
|
|
154
|
+
* Not play mode, the hand is not over, or the table is broken.
|
|
155
|
+
*
|
|
156
|
+
* # Examples
|
|
157
|
+
*
|
|
158
|
+
* ```
|
|
159
|
+
* use bowery::wasm::BoweryTable;
|
|
160
|
+
*
|
|
161
|
+
* let mut table = BoweryTable::new("
|
|
162
|
+
* mode: play
|
|
163
|
+
* table: { button: 0, stakes: { small_blind: 1.0, big_blind: 2.0 } }
|
|
164
|
+
* players: [ { seat: 0, stack: 200 }, { seat: 1, stack: 200, bot: abc } ]
|
|
165
|
+
* ").unwrap();
|
|
166
|
+
* table.act(r#"{"action":"fold"}"#).unwrap();
|
|
167
|
+
* while table.advance().unwrap() != "over" {}
|
|
168
|
+
* assert!(table.next_hand().is_ok());
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
next_hand() {
|
|
172
|
+
const ret = wasm.bowerytable_next_hand(this.__wbg_ptr);
|
|
173
|
+
if (ret[1]) {
|
|
174
|
+
throw takeFromExternrefTable0(ret[0]);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Moves to moment `index`, from 0; past the end, to the last.
|
|
179
|
+
*
|
|
180
|
+
* # Examples
|
|
181
|
+
*
|
|
182
|
+
* ```
|
|
183
|
+
* use bowery::wasm::BoweryTable;
|
|
184
|
+
*
|
|
185
|
+
* let mut table = BoweryTable::new("
|
|
186
|
+
* mode: play
|
|
187
|
+
* table: { stakes: { small_blind: 1.0, big_blind: 2.0 } }
|
|
188
|
+
* players: [ { seat: 0, stack: 200 }, { seat: 1, stack: 200, bot: abc } ]
|
|
189
|
+
* ").unwrap();
|
|
190
|
+
* table.seek(0);
|
|
191
|
+
* assert!(table.state().contains(r#""moment":1"#));
|
|
192
|
+
* ```
|
|
193
|
+
* @param {number} index
|
|
194
|
+
*/
|
|
195
|
+
seek(index) {
|
|
196
|
+
wasm.bowerytable_seek(this.__wbg_ptr, index);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Draws in `layout` from now on: `ring`, `spotlight` or `ledger`.
|
|
200
|
+
*
|
|
201
|
+
* # Errors
|
|
202
|
+
*
|
|
203
|
+
* Any other name.
|
|
204
|
+
*
|
|
205
|
+
* # Examples
|
|
206
|
+
*
|
|
207
|
+
* ```
|
|
208
|
+
* use bowery::wasm::BoweryTable;
|
|
209
|
+
*
|
|
210
|
+
* let mut table = BoweryTable::new("
|
|
211
|
+
* mode: play
|
|
212
|
+
* table: { stakes: { small_blind: 1.0, big_blind: 2.0 } }
|
|
213
|
+
* players: [ { seat: 0, stack: 200 }, { seat: 1, stack: 200, bot: abc } ]
|
|
214
|
+
* ").unwrap();
|
|
215
|
+
* assert!(table.set_layout("ledger").is_ok());
|
|
216
|
+
* ```
|
|
217
|
+
* @param {string} layout
|
|
218
|
+
*/
|
|
219
|
+
set_layout(layout) {
|
|
220
|
+
const ptr0 = passStringToWasm0(layout, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
221
|
+
const len0 = WASM_VECTOR_LEN;
|
|
222
|
+
const ret = wasm.bowerytable_set_layout(this.__wbg_ptr, ptr0, len0);
|
|
223
|
+
if (ret[1]) {
|
|
224
|
+
throw takeFromExternrefTable0(ret[0]);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Draws in `view` from now on: `all` or `seat`.
|
|
229
|
+
*
|
|
230
|
+
* # Errors
|
|
231
|
+
*
|
|
232
|
+
* Any other name.
|
|
233
|
+
*
|
|
234
|
+
* # Examples
|
|
235
|
+
*
|
|
236
|
+
* ```
|
|
237
|
+
* use bowery::wasm::BoweryTable;
|
|
238
|
+
*
|
|
239
|
+
* let mut table = BoweryTable::new("
|
|
240
|
+
* mode: play
|
|
241
|
+
* table: { stakes: { small_blind: 1.0, big_blind: 2.0 } }
|
|
242
|
+
* players: [ { seat: 0, stack: 200 }, { seat: 1, stack: 200, bot: abc } ]
|
|
243
|
+
* ").unwrap();
|
|
244
|
+
* assert!(table.set_view("all").is_ok());
|
|
245
|
+
* ```
|
|
246
|
+
* @param {string} view
|
|
247
|
+
*/
|
|
248
|
+
set_view(view) {
|
|
249
|
+
const ptr0 = passStringToWasm0(view, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
250
|
+
const len0 = WASM_VECTOR_LEN;
|
|
251
|
+
const ret = wasm.bowerytable_set_view(this.__wbg_ptr, ptr0, len0);
|
|
252
|
+
if (ret[1]) {
|
|
253
|
+
throw takeFromExternrefTable0(ret[0]);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Play: a new game seeded with `seed`.
|
|
258
|
+
*
|
|
259
|
+
* # Errors
|
|
260
|
+
*
|
|
261
|
+
* Not play mode.
|
|
262
|
+
*
|
|
263
|
+
* # Examples
|
|
264
|
+
*
|
|
265
|
+
* ```
|
|
266
|
+
* use bowery::wasm::BoweryTable;
|
|
267
|
+
*
|
|
268
|
+
* let mut table = BoweryTable::new("
|
|
269
|
+
* mode: play
|
|
270
|
+
* table: { stakes: { small_blind: 1.0, big_blind: 2.0 } }
|
|
271
|
+
* players: [ { seat: 0, stack: 200 }, { seat: 1, stack: 200, bot: abc } ]
|
|
272
|
+
* ").unwrap();
|
|
273
|
+
* assert!(table.shuffle(9).is_ok());
|
|
274
|
+
* ```
|
|
275
|
+
* @param {number} seed
|
|
276
|
+
*/
|
|
277
|
+
shuffle(seed) {
|
|
278
|
+
const ret = wasm.bowerytable_shuffle(this.__wbg_ptr, seed);
|
|
279
|
+
if (ret[1]) {
|
|
280
|
+
throw takeFromExternrefTable0(ret[0]);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* The controls' state as JSON (see [`crate::element::State`]).
|
|
285
|
+
*
|
|
286
|
+
* # Examples
|
|
287
|
+
*
|
|
288
|
+
* ```
|
|
289
|
+
* use bowery::wasm::BoweryTable;
|
|
290
|
+
*
|
|
291
|
+
* let table = BoweryTable::new("
|
|
292
|
+
* mode: play
|
|
293
|
+
* table: { stakes: { small_blind: 1.0, big_blind: 2.0 } }
|
|
294
|
+
* players: [ { seat: 0, stack: 200 }, { seat: 1, stack: 200, bot: abc } ]
|
|
295
|
+
* ").unwrap();
|
|
296
|
+
* assert!(table.state().contains(r#""moment":1"#));
|
|
297
|
+
* ```
|
|
298
|
+
* @returns {string}
|
|
299
|
+
*/
|
|
300
|
+
state() {
|
|
301
|
+
let deferred1_0;
|
|
302
|
+
let deferred1_1;
|
|
303
|
+
try {
|
|
304
|
+
const ret = wasm.bowerytable_state(this.__wbg_ptr);
|
|
305
|
+
deferred1_0 = ret[0];
|
|
306
|
+
deferred1_1 = ret[1];
|
|
307
|
+
return getStringFromWasm0(ret[0], ret[1]);
|
|
308
|
+
} finally {
|
|
309
|
+
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* The table on screen as SVG, `width` pixels wide, dark or light.
|
|
314
|
+
*
|
|
315
|
+
* # Examples
|
|
316
|
+
*
|
|
317
|
+
* ```
|
|
318
|
+
* use bowery::wasm::BoweryTable;
|
|
319
|
+
*
|
|
320
|
+
* let table = BoweryTable::new("
|
|
321
|
+
* mode: play
|
|
322
|
+
* table: { stakes: { small_blind: 1.0, big_blind: 2.0 } }
|
|
323
|
+
* players: [ { seat: 0, stack: 200 }, { seat: 1, stack: 200, bot: abc } ]
|
|
324
|
+
* ").unwrap();
|
|
325
|
+
* assert!(table.svg(400, true).contains(r#"width="400""#));
|
|
326
|
+
* ```
|
|
327
|
+
* @param {number} width
|
|
328
|
+
* @param {boolean} dark
|
|
329
|
+
* @returns {string}
|
|
330
|
+
*/
|
|
331
|
+
svg(width, dark) {
|
|
332
|
+
let deferred1_0;
|
|
333
|
+
let deferred1_1;
|
|
334
|
+
try {
|
|
335
|
+
const ret = wasm.bowerytable_svg(this.__wbg_ptr, width, dark);
|
|
336
|
+
deferred1_0 = ret[0];
|
|
337
|
+
deferred1_1 = ret[1];
|
|
338
|
+
return getStringFromWasm0(ret[0], ret[1]);
|
|
339
|
+
} finally {
|
|
340
|
+
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* The reader takes `seat` (interactive).
|
|
345
|
+
*
|
|
346
|
+
* # Errors
|
|
347
|
+
*
|
|
348
|
+
* The game's reason, as a string.
|
|
349
|
+
*
|
|
350
|
+
* # Examples
|
|
351
|
+
*
|
|
352
|
+
* ```
|
|
353
|
+
* use bowery::wasm::BoweryTable;
|
|
354
|
+
*
|
|
355
|
+
* let mut table = BoweryTable::new("
|
|
356
|
+
* mode: interactive
|
|
357
|
+
* hand:
|
|
358
|
+
* hand: { id: ex, game: holdem }
|
|
359
|
+
* table: { seats: 2, button: 0, stakes: { small_blind: 1.0, big_blind: 2.0 } }
|
|
360
|
+
* players: [ { seat: 0, name: A, stack: 100.0 }, { seat: 1, name: B, stack: 100.0 } ]
|
|
361
|
+
* streets: { preflop: { actions: [ { seat: 0, action: fold } ] } }
|
|
362
|
+
* ").unwrap();
|
|
363
|
+
* assert!(table.take_seat(0).is_ok());
|
|
364
|
+
* ```
|
|
365
|
+
* @param {number} seat
|
|
366
|
+
*/
|
|
367
|
+
take_seat(seat) {
|
|
368
|
+
const ret = wasm.bowerytable_take_seat(this.__wbg_ptr, seat);
|
|
369
|
+
if (ret[1]) {
|
|
370
|
+
throw takeFromExternrefTable0(ret[0]);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* This crate's version.
|
|
375
|
+
*
|
|
376
|
+
* # Examples
|
|
377
|
+
*
|
|
378
|
+
* ```
|
|
379
|
+
* use bowery::wasm::BoweryTable;
|
|
380
|
+
*
|
|
381
|
+
* assert_eq!(env!("CARGO_PKG_VERSION"), BoweryTable::version());
|
|
382
|
+
* ```
|
|
383
|
+
* @returns {string}
|
|
384
|
+
*/
|
|
385
|
+
static version() {
|
|
386
|
+
let deferred1_0;
|
|
387
|
+
let deferred1_1;
|
|
388
|
+
try {
|
|
389
|
+
const ret = wasm.bowerytable_version();
|
|
390
|
+
deferred1_0 = ret[0];
|
|
391
|
+
deferred1_1 = ret[1];
|
|
392
|
+
return getStringFromWasm0(ret[0], ret[1]);
|
|
393
|
+
} finally {
|
|
394
|
+
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (Symbol.dispose) BoweryTable.prototype[Symbol.dispose] = BoweryTable.prototype.free;
|
|
399
|
+
function __wbg_get_imports() {
|
|
400
|
+
const import0 = {
|
|
401
|
+
__proto__: null,
|
|
402
|
+
__wbg___wbindgen_throw_41e9ee4f547fc59a: function(arg0, arg1) {
|
|
403
|
+
throw new Error(getStringFromWasm0(arg0, arg1));
|
|
404
|
+
},
|
|
405
|
+
__wbindgen_generic_0000000000000001: function(arg0, arg1) {
|
|
406
|
+
// Cast intrinsic for `Ref(String) -> Externref`.
|
|
407
|
+
const ret = getStringFromWasm0(arg0, arg1);
|
|
408
|
+
return ret;
|
|
409
|
+
},
|
|
410
|
+
__wbindgen_init_externref_table: function() {
|
|
411
|
+
const table = wasm.__wbindgen_externrefs;
|
|
412
|
+
const offset = table.grow(4);
|
|
413
|
+
table.set(0, undefined);
|
|
414
|
+
table.set(offset + 0, undefined);
|
|
415
|
+
table.set(offset + 1, null);
|
|
416
|
+
table.set(offset + 2, true);
|
|
417
|
+
table.set(offset + 3, false);
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
return {
|
|
421
|
+
__proto__: null,
|
|
422
|
+
"./bowery_wasm_bg.js": import0,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const BoweryTableFinalization = (typeof FinalizationRegistry === 'undefined')
|
|
427
|
+
? { register: () => {}, unregister: () => {} }
|
|
428
|
+
: new FinalizationRegistry(ptr => wasm.__wbg_bowerytable_free(ptr, 1));
|
|
429
|
+
|
|
430
|
+
function getStringFromWasm0(ptr, len) {
|
|
431
|
+
return decodeText(ptr >>> 0, len);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
let cachedUint8ArrayMemory0 = null;
|
|
435
|
+
function getUint8ArrayMemory0() {
|
|
436
|
+
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
|
|
437
|
+
cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
|
|
438
|
+
}
|
|
439
|
+
return cachedUint8ArrayMemory0;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function passStringToWasm0(arg, malloc, realloc) {
|
|
443
|
+
if (realloc === undefined) {
|
|
444
|
+
const buf = cachedTextEncoder.encode(arg);
|
|
445
|
+
const ptr = malloc(buf.length, 1) >>> 0;
|
|
446
|
+
getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
|
|
447
|
+
WASM_VECTOR_LEN = buf.length;
|
|
448
|
+
return ptr;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
let len = arg.length;
|
|
452
|
+
let ptr = malloc(len, 1) >>> 0;
|
|
453
|
+
|
|
454
|
+
const mem = getUint8ArrayMemory0();
|
|
455
|
+
|
|
456
|
+
let offset = 0;
|
|
457
|
+
|
|
458
|
+
for (; offset < len; offset++) {
|
|
459
|
+
const code = arg.charCodeAt(offset);
|
|
460
|
+
if (code > 0x7F) break;
|
|
461
|
+
mem[ptr + offset] = code;
|
|
462
|
+
}
|
|
463
|
+
if (offset !== len) {
|
|
464
|
+
if (offset !== 0) {
|
|
465
|
+
arg = arg.slice(offset);
|
|
466
|
+
}
|
|
467
|
+
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
|
|
468
|
+
const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
|
|
469
|
+
const ret = cachedTextEncoder.encodeInto(arg, view);
|
|
470
|
+
|
|
471
|
+
offset += ret.written;
|
|
472
|
+
ptr = realloc(ptr, len, offset, 1) >>> 0;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
WASM_VECTOR_LEN = offset;
|
|
476
|
+
return ptr;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function takeFromExternrefTable0(idx) {
|
|
480
|
+
const value = wasm.__wbindgen_externrefs.get(idx);
|
|
481
|
+
wasm.__externref_table_dealloc(idx);
|
|
482
|
+
return value;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
|
486
|
+
cachedTextDecoder.decode();
|
|
487
|
+
const MAX_SAFARI_DECODE_BYTES = 2146435072;
|
|
488
|
+
let numBytesDecoded = 0;
|
|
489
|
+
function decodeText(ptr, len) {
|
|
490
|
+
numBytesDecoded += len;
|
|
491
|
+
if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
|
|
492
|
+
cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
|
493
|
+
cachedTextDecoder.decode();
|
|
494
|
+
numBytesDecoded = len;
|
|
495
|
+
}
|
|
496
|
+
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const cachedTextEncoder = new TextEncoder();
|
|
500
|
+
|
|
501
|
+
if (!('encodeInto' in cachedTextEncoder)) {
|
|
502
|
+
cachedTextEncoder.encodeInto = function (arg, view) {
|
|
503
|
+
const buf = cachedTextEncoder.encode(arg);
|
|
504
|
+
view.set(buf);
|
|
505
|
+
return {
|
|
506
|
+
read: arg.length,
|
|
507
|
+
written: buf.length
|
|
508
|
+
};
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
let WASM_VECTOR_LEN = 0;
|
|
513
|
+
|
|
514
|
+
let wasmModule, wasmInstance, wasm;
|
|
515
|
+
function __wbg_finalize_init(instance, module) {
|
|
516
|
+
wasmInstance = instance;
|
|
517
|
+
wasm = instance.exports;
|
|
518
|
+
wasmModule = module;
|
|
519
|
+
cachedUint8ArrayMemory0 = null;
|
|
520
|
+
wasm.__wbindgen_start();
|
|
521
|
+
return wasm;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
async function __wbg_load(module, imports) {
|
|
525
|
+
if (typeof Response === 'function' && module instanceof Response) {
|
|
526
|
+
if (!module.ok) {
|
|
527
|
+
throw new Error(`failed to fetch Wasm: ${module.status} ${module.statusText} fetching '${module.url}'`);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (typeof WebAssembly.instantiateStreaming === 'function') {
|
|
531
|
+
try {
|
|
532
|
+
return await WebAssembly.instantiateStreaming(module, imports);
|
|
533
|
+
} catch (e) {
|
|
534
|
+
const validResponse = expectedResponseType(module.type);
|
|
535
|
+
|
|
536
|
+
if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') {
|
|
537
|
+
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);
|
|
538
|
+
|
|
539
|
+
} else { throw e; }
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const bytes = await module.arrayBuffer();
|
|
544
|
+
return await WebAssembly.instantiate(bytes, imports);
|
|
545
|
+
} else {
|
|
546
|
+
const instance = await WebAssembly.instantiate(module, imports);
|
|
547
|
+
|
|
548
|
+
if (instance instanceof WebAssembly.Instance) {
|
|
549
|
+
return { instance, module };
|
|
550
|
+
} else {
|
|
551
|
+
return instance;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function expectedResponseType(type) {
|
|
556
|
+
switch (type) {
|
|
557
|
+
case 'basic': case 'cors': case 'default': return true;
|
|
558
|
+
}
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function initSync(module) {
|
|
564
|
+
if (wasm !== undefined) return wasm;
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
if (module !== undefined) {
|
|
568
|
+
if (Object.getPrototypeOf(module) === Object.prototype) {
|
|
569
|
+
({module} = module)
|
|
570
|
+
} else {
|
|
571
|
+
console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const imports = __wbg_get_imports();
|
|
576
|
+
if (!(module instanceof WebAssembly.Module)) {
|
|
577
|
+
module = new WebAssembly.Module(module);
|
|
578
|
+
}
|
|
579
|
+
const instance = new WebAssembly.Instance(module, imports);
|
|
580
|
+
return __wbg_finalize_init(instance, module);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
async function __wbg_init(module_or_path) {
|
|
584
|
+
if (wasm !== undefined) return wasm;
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
if (module_or_path !== undefined) {
|
|
588
|
+
if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
|
|
589
|
+
({module_or_path} = module_or_path)
|
|
590
|
+
} else {
|
|
591
|
+
console.warn('using deprecated parameters for the initialization function; pass a single object instead')
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (module_or_path === undefined) {
|
|
596
|
+
module_or_path = new URL('bowery_wasm_bg.wasm', import.meta.url);
|
|
597
|
+
}
|
|
598
|
+
const imports = __wbg_get_imports();
|
|
599
|
+
|
|
600
|
+
if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
|
|
601
|
+
module_or_path = fetch(module_or_path);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const { instance, module } = await __wbg_load(await module_or_path, imports);
|
|
605
|
+
|
|
606
|
+
return __wbg_finalize_init(instance, module);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
export { initSync, __wbg_init as default };
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,8 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imperialbower/bowery",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "One poker table control, configured not compiled: <bowery-table>.
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "One poker table control, configured not compiled: <bowery-table>.",
|
|
5
5
|
"license": "MIT OR Apache-2.0",
|
|
6
|
-
"
|
|
7
|
-
"
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "bowery.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./bowery.js",
|
|
10
|
+
"./bowery.css": "./bowery.css"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bowery.js",
|
|
14
|
+
"bowery.css",
|
|
15
|
+
"bowery_wasm.js",
|
|
16
|
+
"bowery_wasm_bg.wasm",
|
|
17
|
+
"LICENSE-MIT",
|
|
18
|
+
"LICENSE-APACHE",
|
|
19
|
+
"MANIFEST.sha256",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"sideEffects": true,
|
|
23
|
+
"keywords": ["poker", "web-component", "custom-element", "wasm", "svg", "hand-history"],
|
|
24
|
+
"homepage": "https://github.com/ImperialBower/bowery",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/ImperialBower/bowery.git"
|
|
28
|
+
},
|
|
29
|
+
"bugs": "https://github.com/ImperialBower/bowery/issues"
|
|
8
30
|
}
|