Skip to content

Commit 9d74966

Browse files
committed
allow setting bot options from Debug Panel
1 parent 6b5ab29 commit 9d74966

3 files changed

Lines changed: 80 additions & 50 deletions

File tree

src/ai/ai.test.js

Lines changed: 38 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -233,12 +233,6 @@ describe('Bot', () => {
233233
});
234234

235235
describe('MCTSBot', () => {
236-
test('defaults', () => {
237-
const b = new MCTSBot({ game: TicTacToe });
238-
expect(b.iterations()).toBe(1000);
239-
expect(b.playoutDepth()).toBe(50);
240-
});
241-
242236
test('game that never ends', () => {
243237
const game = {};
244238
const state = InitializeGame({ game });
@@ -351,45 +345,53 @@ describe('MCTSBot', () => {
351345
}
352346
});
353347

354-
test('iterations & playout depth settings', () => {
355-
const state = InitializeGame({ game: TicTacToe });
348+
describe('iterations & playout depth', () => {
349+
test('set opts', () => {
350+
const bot = new MCTSBot({ game: TicTacToe, enumerate: jest.fn() });
351+
bot.setOpt('iterations', 1);
352+
expect(bot.opts()['iterations'].value).toBe(1);
353+
});
356354

357-
// jump ahead in the game because the example iterations
358-
// and playoutDepth functions are based on the turn
359-
state.ctx.turn = 8;
355+
test('functions', () => {
356+
const state = InitializeGame({ game: TicTacToe });
360357

361-
const { turn, currentPlayer } = state.ctx;
358+
// jump ahead in the game because the example iterations
359+
// and playoutDepth functions are based on the turn
360+
state.ctx.turn = 8;
362361

363-
const enumerateSpy = jest.fn(enumerate);
362+
const { turn, currentPlayer } = state.ctx;
364363

365-
const bot = new MCTSBot({
366-
game: TicTacToe,
367-
enumerate: enumerateSpy,
368-
iterations: (G, ctx) => ctx.turn * 100,
369-
playoutDepth: (G, ctx) => ctx.turn * 10,
370-
});
364+
const enumerateSpy = jest.fn(enumerate);
365+
366+
const bot = new MCTSBot({
367+
game: TicTacToe,
368+
enumerate: enumerateSpy,
369+
iterations: (G, ctx) => ctx.turn * 100,
370+
playoutDepth: (G, ctx) => ctx.turn * 10,
371+
});
371372

372-
expect(bot.iterations(null, { turn }, currentPlayer)).toBe(turn * 100);
373-
expect(bot.playoutDepth(null, { turn }, currentPlayer)).toBe(turn * 10);
373+
expect(bot.iterations(null, { turn }, currentPlayer)).toBe(turn * 100);
374+
expect(bot.playoutDepth(null, { turn }, currentPlayer)).toBe(turn * 10);
374375

375-
// try the playout() function which requests the playoutDepth value
376-
bot.playout({ state });
376+
// try the playout() function which requests the playoutDepth value
377+
bot.playout({ state });
377378

378-
expect(enumerateSpy).toHaveBeenCalledWith(
379-
state.G,
380-
state.ctx,
381-
currentPlayer
382-
);
379+
expect(enumerateSpy).toHaveBeenCalledWith(
380+
state.G,
381+
state.ctx,
382+
currentPlayer
383+
);
383384

384-
// then try the play() function which requests the iterations value
385-
enumerateSpy.mockClear();
385+
// then try the play() function which requests the iterations value
386+
enumerateSpy.mockClear();
386387

387-
bot.play(state, currentPlayer);
388+
bot.play(state, currentPlayer);
388389

389-
expect(enumerateSpy).toHaveBeenCalledWith(
390-
state.G,
391-
state.ctx,
392-
currentPlayer
393-
);
390+
expect(enumerateSpy).toHaveBeenCalledWith(
391+
state.G,
392+
state.ctx,
393+
currentPlayer
394+
);
395+
});
394396
});
395397
});

src/ai/bot.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,26 @@ export class Bot {
1616
constructor({ enumerate, seed }) {
1717
this.enumerateFn = enumerate;
1818
this.seed = seed;
19+
this._opts = {};
20+
}
21+
22+
addOpt({ key, range, initial }) {
23+
this._opts[key] = {
24+
range,
25+
value: initial,
26+
};
27+
}
28+
29+
getOpt(key) {
30+
return this._opts[key].value;
31+
}
32+
33+
setOpt(key, value) {
34+
this._opts[key].value = value;
35+
}
36+
37+
opts() {
38+
return this._opts;
1939
}
2040

2141
enumerate = (G, ctx, playerID) => {

src/ai/mcts-bot.js

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,22 @@ export class MCTSBot extends Bot {
1919
objectives = () => ({});
2020
}
2121

22-
if (typeof iterations === 'number') {
23-
const iter8ns = iterations;
24-
iterations = () => iter8ns;
25-
}
26-
27-
if (typeof playoutDepth === 'number') {
28-
const depth = playoutDepth;
29-
playoutDepth = () => depth;
30-
}
31-
3222
this.objectives = objectives;
3323
this.reducer = CreateGameReducer({ game });
34-
this.iterations = iterations || (() => 1000);
35-
this.playoutDepth = playoutDepth || (() => 50);
24+
this.iterations = iterations;
25+
this.playoutDepth = playoutDepth;
26+
27+
this.addOpt({
28+
key: 'iterations',
29+
initial: typeof iterations === 'number' ? iterations : 1000,
30+
range: { min: 1, max: 2000 },
31+
});
32+
33+
this.addOpt({
34+
key: 'playoutDepth',
35+
initial: typeof playoutDepth === 'number' ? playoutDepth : 50,
36+
range: { min: 1, max: 100 },
37+
});
3638
}
3739

3840
createNode({ state, parentAction, parent, playerID }) {
@@ -127,7 +129,10 @@ export class MCTSBot extends Bot {
127129
playout(node) {
128130
let state = node.state;
129131

130-
const playoutDepth = this.playoutDepth(state.G, state.ctx);
132+
let playoutDepth = this.getOpt('playoutDepth');
133+
if (typeof this.playoutDepth === 'function') {
134+
playoutDepth = this.playoutDepth(state.G, state.ctx);
135+
}
131136

132137
for (let i = 0; i < playoutDepth && state.ctx.gameover === undefined; i++) {
133138
const { G, ctx } = state;
@@ -190,7 +195,10 @@ export class MCTSBot extends Bot {
190195
play(state, playerID) {
191196
const root = this.createNode({ state, playerID });
192197

193-
const iterations = this.iterations(state.G, state.ctx);
198+
let iterations = this.getOpt('iterations');
199+
if (typeof this.iterations === 'function') {
200+
iterations = this.iterations(state.G, state.ctx);
201+
}
194202

195203
for (let i = 0; i < iterations; i++) {
196204
const leaf = this.select(root);

0 commit comments

Comments
 (0)