Skip to content

Commit

Permalink
allow setting bot options from Debug Panel
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolodavis committed Nov 7, 2019
1 parent 6b5ab29 commit 9d74966
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 50 deletions.
74 changes: 38 additions & 36 deletions src/ai/ai.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,12 +233,6 @@ describe('Bot', () => {
});

describe('MCTSBot', () => {
test('defaults', () => {
const b = new MCTSBot({ game: TicTacToe });
expect(b.iterations()).toBe(1000);
expect(b.playoutDepth()).toBe(50);
});

test('game that never ends', () => {
const game = {};
const state = InitializeGame({ game });
Expand Down Expand Up @@ -351,45 +345,53 @@ describe('MCTSBot', () => {
}
});

test('iterations & playout depth settings', () => {
const state = InitializeGame({ game: TicTacToe });
describe('iterations & playout depth', () => {
test('set opts', () => {
const bot = new MCTSBot({ game: TicTacToe, enumerate: jest.fn() });
bot.setOpt('iterations', 1);
expect(bot.opts()['iterations'].value).toBe(1);
});

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

const { turn, currentPlayer } = state.ctx;
// jump ahead in the game because the example iterations
// and playoutDepth functions are based on the turn
state.ctx.turn = 8;

const enumerateSpy = jest.fn(enumerate);
const { turn, currentPlayer } = state.ctx;

const bot = new MCTSBot({
game: TicTacToe,
enumerate: enumerateSpy,
iterations: (G, ctx) => ctx.turn * 100,
playoutDepth: (G, ctx) => ctx.turn * 10,
});
const enumerateSpy = jest.fn(enumerate);

const bot = new MCTSBot({
game: TicTacToe,
enumerate: enumerateSpy,
iterations: (G, ctx) => ctx.turn * 100,
playoutDepth: (G, ctx) => ctx.turn * 10,
});

expect(bot.iterations(null, { turn }, currentPlayer)).toBe(turn * 100);
expect(bot.playoutDepth(null, { turn }, currentPlayer)).toBe(turn * 10);
expect(bot.iterations(null, { turn }, currentPlayer)).toBe(turn * 100);
expect(bot.playoutDepth(null, { turn }, currentPlayer)).toBe(turn * 10);

// try the playout() function which requests the playoutDepth value
bot.playout({ state });
// try the playout() function which requests the playoutDepth value
bot.playout({ state });

expect(enumerateSpy).toHaveBeenCalledWith(
state.G,
state.ctx,
currentPlayer
);
expect(enumerateSpy).toHaveBeenCalledWith(
state.G,
state.ctx,
currentPlayer
);

// then try the play() function which requests the iterations value
enumerateSpy.mockClear();
// then try the play() function which requests the iterations value
enumerateSpy.mockClear();

bot.play(state, currentPlayer);
bot.play(state, currentPlayer);

expect(enumerateSpy).toHaveBeenCalledWith(
state.G,
state.ctx,
currentPlayer
);
expect(enumerateSpy).toHaveBeenCalledWith(
state.G,
state.ctx,
currentPlayer
);
});
});
});
20 changes: 20 additions & 0 deletions src/ai/bot.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,26 @@ export class Bot {
constructor({ enumerate, seed }) {
this.enumerateFn = enumerate;
this.seed = seed;
this._opts = {};
}

addOpt({ key, range, initial }) {
this._opts[key] = {
range,
value: initial,
};
}

getOpt(key) {
return this._opts[key].value;
}

setOpt(key, value) {
this._opts[key].value = value;
}

opts() {
return this._opts;
}

enumerate = (G, ctx, playerID) => {
Expand Down
36 changes: 22 additions & 14 deletions src/ai/mcts-bot.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,22 @@ export class MCTSBot extends Bot {
objectives = () => ({});
}

if (typeof iterations === 'number') {
const iter8ns = iterations;
iterations = () => iter8ns;
}

if (typeof playoutDepth === 'number') {
const depth = playoutDepth;
playoutDepth = () => depth;
}

this.objectives = objectives;
this.reducer = CreateGameReducer({ game });
this.iterations = iterations || (() => 1000);
this.playoutDepth = playoutDepth || (() => 50);
this.iterations = iterations;
this.playoutDepth = playoutDepth;

this.addOpt({
key: 'iterations',
initial: typeof iterations === 'number' ? iterations : 1000,
range: { min: 1, max: 2000 },
});

this.addOpt({
key: 'playoutDepth',
initial: typeof playoutDepth === 'number' ? playoutDepth : 50,
range: { min: 1, max: 100 },
});
}

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

const playoutDepth = this.playoutDepth(state.G, state.ctx);
let playoutDepth = this.getOpt('playoutDepth');
if (typeof this.playoutDepth === 'function') {
playoutDepth = this.playoutDepth(state.G, state.ctx);
}

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

const iterations = this.iterations(state.G, state.ctx);
let iterations = this.getOpt('iterations');
if (typeof this.iterations === 'function') {
iterations = this.iterations(state.G, state.ctx);
}

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

0 comments on commit 9d74966

Please sign in to comment.