Skip to content

Commit 00f0d27

Browse files
simple game tests added
1 parent 4ae9ce0 commit 00f0d27

File tree

1 file changed

+192
-55
lines changed

1 file changed

+192
-55
lines changed

test/others/minimax.spec.js

Lines changed: 192 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,162 @@
11
const minimaxBuilder = require('../../src/others/minimax.js').minimaxBuilder;
22

3-
describe('Minimax with tic tac toe', function () {
3+
describe('Minimax', function () {
44
'use strict';
55

6-
let game = ticTacToe();
6+
it('builder should be defined', function () {
7+
expect(minimaxBuilder).toBeDefined();
8+
});
79

8-
function getAllNextStates(state) {
9-
const possibleMoves = game.emptyCells(state);
10+
describe('with tic tac toe', function () {
11+
let game = ticTacToe();
1012

11-
return possibleMoves.map(move => ({
12-
move,
13-
state: game.nextState(state, move),
14-
}));
15-
}
13+
function getAllNextStates(state) {
14+
const possibleMoves = game.emptyCells(state);
1615

17-
const minimaxForX = minimaxBuilder(
18-
getAllNextStates,
19-
state => game.isGameOver(state),
20-
state => game.getScore(state).x - game.getScore(state).o
21-
)
16+
return possibleMoves.map(move => ({
17+
move,
18+
state: game.nextState(state, move),
19+
}));
20+
}
2221

23-
const minimaxForO = minimaxBuilder(
24-
getAllNextStates,
25-
state => game.isGameOver(state),
26-
state => game.getScore(state).o - game.getScore(state).x
27-
)
22+
const minimaxForX = minimaxBuilder(
23+
getAllNextStates,
24+
state => game.isGameOver(state),
25+
state => game.getScore(state).x - game.getScore(state).o
26+
)
2827

29-
it('should be defined', function () {
30-
expect(minimaxBuilder).toBeDefined();
31-
});
28+
const minimaxForO = minimaxBuilder(
29+
getAllNextStates,
30+
state => game.isGameOver(state),
31+
state => game.getScore(state).o - game.getScore(state).x
32+
)
3233

33-
it('should win versus dumb agent as first player', function () {
34-
let state = game.newState('x');
34+
it('should win versus dumb agent as first player', function () {
35+
let state = game.newState('x');
3536

36-
while (!game.isGameOver(state)) {
37-
if (state.turn === 'x') {
38-
state = game.nextState(state, minimaxForX(state, true, 5, -Infinity, Infinity).move);
39-
} else {
40-
const move = game.emptyCells(state)[0];
41-
state = game.nextState(state, move);
37+
while (!game.isGameOver(state)) {
38+
if (state.turn === 'x') {
39+
state = game.nextState(state, minimaxForX(state, true, 5, -Infinity, Infinity).move);
40+
} else {
41+
const move = game.emptyCells(state)[0];
42+
state = game.nextState(state, move);
43+
}
4244
}
43-
}
4445

45-
expect(game.isGameOver(state)).toBe(true);
46-
expect(game.getScore(state)).toEqual({x: 1, o: 0});
47-
});
46+
expect(game.isGameOver(state)).toBe(true);
47+
expect(game.getScore(state)).toEqual({x: 1, o: 0});
48+
});
4849

49-
it('should win versus dumb agent as second player', function () {
50-
let state = game.newState('x');
50+
it('should win versus dumb agent as second player', function () {
51+
let state = game.newState('x');
5152

52-
while (!game.isGameOver(state)) {
53-
if (state.turn === 'o') {
54-
state = game.nextState(state, minimaxForO(state, true, 5, -Infinity, Infinity).move);
55-
} else {
56-
const move = game.emptyCells(state)[0];
57-
state = game.nextState(state, move);
53+
while (!game.isGameOver(state)) {
54+
if (state.turn === 'o') {
55+
state = game.nextState(state, minimaxForO(state, true, 5, -Infinity, Infinity).move);
56+
} else {
57+
const move = game.emptyCells(state)[0];
58+
state = game.nextState(state, move);
59+
}
5860
}
59-
}
6061

61-
expect(game.isGameOver(state)).toBe(true);
62-
expect(game.getScore(state)).toEqual({x: 0, o: 1});
62+
expect(game.isGameOver(state)).toBe(true);
63+
expect(game.getScore(state)).toEqual({x: 0, o: 1});
64+
});
65+
66+
67+
it('should be a tie for two minimax agents', function () {
68+
let state = game.newState('x');
69+
70+
while (!game.isGameOver(state)) {
71+
if (state.turn === 'o') {
72+
state = game.nextState(state, minimaxForO(state, true, 5, -Infinity, Infinity).move);
73+
} else {
74+
state = game.nextState(state, minimaxForX(state, true, 5, -Infinity, Infinity).move);
75+
}
76+
}
77+
expect(game.isGameOver(state)).toBe(true);
78+
expect(game.getScore(state)).toEqual({x: 0, o: 0});
79+
});
6380
});
6481

82+
describe('with simple game', function () {
83+
let game = simpleGame();
84+
85+
const minimaxForA = minimaxBuilder(
86+
state => [true, false].map(move => ({ move, state: game.nextState(state, move)})),
87+
state => game.isGameOver(state),
88+
state => game.getScore(state).A - game.getScore(state).B
89+
);
90+
const minimaxForB = minimaxBuilder(
91+
state => [true, false].map(move => ({ move, state: game.nextState(state, move)})),
92+
state => game.isGameOver(state),
93+
state => game.getScore(state).B - game.getScore(state).A
94+
);
6595

66-
it('should be a tie for two minimax agents', function () {
67-
let state = game.newState('x');
96+
it('should win versus dumb agent as a first player', function () {
97+
/* o
98+
/ \
99+
o o
100+
/ \ / \
101+
-1 1 1 -1
102+
*/
103+
const binaryTree = [0, 0, 0, -1, 1, 1, -1];
104+
let state = game.newState(binaryTree);
68105

69-
while (!game.isGameOver(state)) {
70-
if (state.turn === 'o') {
71-
state = game.nextState(state, minimaxForO(state, true, 5, -Infinity, Infinity).move);
72-
} else {
73-
state = game.nextState(state, minimaxForX(state, true, 5, -Infinity, Infinity).move);
106+
while (!game.isGameOver(state)) {
107+
if (state.turn === 'A') {
108+
state = game.nextState(state, minimaxForA(state, true, 5, -Infinity, Infinity).move);
109+
} else {
110+
state = game.nextState(state, false);
111+
}
74112
}
75-
}
76-
expect(game.isGameOver(state)).toBe(true);
77-
expect(game.getScore(state)).toEqual({x: 0, o: 0});
113+
114+
expect(game.isGameOver(state)).toBe(true);
115+
expect(game.getScore(state)).toEqual({A: 1, B: -1});
116+
});
117+
118+
it('should win versus dumb agent as a second player', function () {
119+
/* o
120+
/ \
121+
o o
122+
/ \ / \
123+
-1 -1 -1 1
124+
*/
125+
const binaryTree = [0, 0, 0, -1, -1, -1, 1];
126+
let state = game.newState(binaryTree);
127+
128+
while (!game.isGameOver(state)) {
129+
if (state.turn === 'B') {
130+
state = game.nextState(state, minimaxForB(state, true, 5, -Infinity, Infinity).move);
131+
} else {
132+
state = game.nextState(state, false);
133+
}
134+
}
135+
136+
expect(game.isGameOver(state)).toBe(true);
137+
expect(game.getScore(state)).toEqual({A: -1, B: 1});
138+
});
139+
140+
it('should be a tie for two minimax agents', function () {
141+
/* o
142+
/ \
143+
o o
144+
/ \ / \
145+
1 -1 -1 -1
146+
*/
147+
const binaryTree = [0, 0, 0, 1, -1, -1, -1];
148+
let state = game.newState(binaryTree);
149+
150+
while (!game.isGameOver(state)) {
151+
if (state.turn === 'A') {
152+
state = game.nextState(state, minimaxForA(state, true, 5, -Infinity, Infinity).move);
153+
} else {
154+
state = game.nextState(state, minimaxForB(state, true, 5, -Infinity, Infinity).move);
155+
}
156+
}
157+
expect(game.isGameOver(state)).toBe(true);
158+
expect(game.getScore(state)).toEqual({A: 1, B: 1});
159+
});
78160
});
79161
});
80162

@@ -164,3 +246,58 @@ function ticTacToe() {
164246
emptyCells,
165247
}
166248
}
249+
250+
251+
/* A simple game made for the purpose of minimax testing. The game has a binary tree with end values: -1 for loss and 1 for the win.
252+
Each player starts from the root node and has a binary choose - "false" moves to the left child and "true" moves to the right child.
253+
The game ends when both players reach the very bottom leaf.
254+
o
255+
/ \
256+
o o
257+
/ \ / \
258+
1 -1 -1 -1
259+
*/
260+
function simpleGame() {
261+
'use strict';
262+
263+
function newState(binaryTree) {
264+
return {
265+
turn: 'A',
266+
tree: binaryTree,
267+
aPosition: 0,
268+
bPosition: 0,
269+
};
270+
}
271+
272+
function nextState(state, move) {
273+
if (state.turn === 'A') {
274+
return Object.assign({}, state, {
275+
aPosition: move ? state.aPosition * 2 + 2 : state.aPosition * 2 + 1,
276+
turn: 'B',
277+
});
278+
} else {
279+
return Object.assign({}, state, {
280+
bPosition: move ? state.bPosition * 2 + 2 : state.bPosition * 2 + 1,
281+
turn: 'A',
282+
});
283+
}
284+
}
285+
286+
function isGameOver(state) {
287+
return state.tree[state.aPosition] !== 0 && state.tree[state.bPosition] !== 0;
288+
}
289+
290+
function getScore(state) {
291+
return {
292+
A: state.tree[state.aPosition],
293+
B: state.tree[state.bPosition],
294+
}
295+
}
296+
297+
return {
298+
newState,
299+
nextState,
300+
isGameOver,
301+
getScore,
302+
}
303+
}

0 commit comments

Comments
 (0)