Skip to content

Commit f19f1de

Browse files
committed
add async mode to MCTS bot
1 parent 7d22a47 commit f19f1de

4 files changed

Lines changed: 67 additions & 16 deletions

File tree

src/ai/ai.test.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,21 @@ describe('MCTSBot', () => {
357357
}
358358
});
359359

360+
test('async mode', async () => {
361+
const initialState = InitializeGame({ game: TicTacToe });
362+
const bot = new MCTSBot({
363+
seed: '0',
364+
game: TicTacToe,
365+
enumerate,
366+
playerID: '0',
367+
iterations: 10,
368+
playoutDepth: 10,
369+
});
370+
bot.setOpt('async', true);
371+
const action = await bot.play(initialState, '0');
372+
expect(action).not.toBeUndefined();
373+
});
374+
360375
describe('iterations & playout depth', () => {
361376
test('set opts', () => {
362377
const bot = new MCTSBot({ game: TicTacToe, enumerate: jest.fn() });

src/ai/bot.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export class Bot {
1616
constructor({ enumerate, seed }) {
1717
this.enumerateFn = enumerate;
1818
this.seed = seed;
19+
this.iterationCounter = 0;
1920
this._opts = {};
2021
}
2122

src/ai/mcts-bot.js

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ export class MCTSBot extends Bot {
2424
this.iterations = iterations;
2525
this.playoutDepth = playoutDepth;
2626

27+
this.addOpt({
28+
key: 'async',
29+
initial: false,
30+
});
31+
2732
this.addOpt({
2833
key: 'iterations',
2934
initial: typeof iterations === 'number' ? iterations : 1000,
@@ -195,19 +200,12 @@ export class MCTSBot extends Bot {
195200
play(state, playerID) {
196201
const root = this.createNode({ state, playerID });
197202

198-
let iterations = this.getOpt('iterations');
203+
let numIterations = this.getOpt('iterations');
199204
if (typeof this.iterations === 'function') {
200-
iterations = this.iterations(state.G, state.ctx);
205+
numIterations = this.iterations(state.G, state.ctx);
201206
}
202207

203-
return new Promise(resolve => {
204-
for (let i = 0; i < iterations; i++) {
205-
const leaf = this.select(root);
206-
const child = this.expand(leaf);
207-
const result = this.playout(child);
208-
this.backpropagate(child, result);
209-
}
210-
208+
const getResult = () => {
211209
let selectedChild = null;
212210
for (const child of root.children) {
213211
if (selectedChild == null || child.visits > selectedChild.visits) {
@@ -217,8 +215,36 @@ export class MCTSBot extends Bot {
217215

218216
const action = selectedChild && selectedChild.parentAction;
219217
const metadata = root;
218+
return { action, metadata };
219+
};
220220

221-
resolve({ action, metadata });
221+
return new Promise(resolve => {
222+
const iteration = () => {
223+
const leaf = this.select(root);
224+
const child = this.expand(leaf);
225+
const result = this.playout(child);
226+
this.backpropagate(child, result);
227+
this.iterationCounter++;
228+
};
229+
230+
this.iterationCounter = 0;
231+
232+
if (this.getOpt('async')) {
233+
const asyncIteration = () => {
234+
if (this.iterationCounter < numIterations) {
235+
iteration();
236+
setTimeout(asyncIteration, 0);
237+
} else {
238+
resolve(getResult());
239+
}
240+
};
241+
asyncIteration();
242+
} else {
243+
while (this.iterationCounter < numIterations) {
244+
iteration();
245+
}
246+
resolve(getResult());
247+
}
222248
});
223249
}
224250
}

src/client/debug/ai/Options.svelte

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,23 @@
2626
.value {
2727
font-weight: bold;
2828
}
29+
30+
input[type='checkbox'] {
31+
vertical-align: middle;
32+
}
2933
</style>
3034

3135
{#each Object.entries(bot.opts()) as [key, value]}
3236
<div class="option">
33-
<label>{key}</label>
34-
<span class="value">{values[key]}</span>
35-
{#if value.range}
36-
<input type=range bind:value={values[key]} min={value.range.min} max={value.range.max} on:change={OnChange}>
37-
{/if}
37+
<label>{key}</label>
38+
39+
{#if value.range}
40+
<span class="value">{values[key]}</span>
41+
<input type=range bind:value={values[key]} min={value.range.min} max={value.range.max} on:change={OnChange}>
42+
{/if}
43+
44+
{#if typeof value.value === 'boolean'}
45+
<input type=checkbox bind:checked={values[key]} on:change={OnChange}>
46+
{/if}
3847
</div>
3948
{/each}

0 commit comments

Comments
 (0)