diff --git a/dist/src/lib/config.d.ts b/dist/src/lib/config.d.ts index 387f1d9..d7db522 100644 --- a/dist/src/lib/config.d.ts +++ b/dist/src/lib/config.d.ts @@ -1,11 +1,11 @@ import { GameFn } from './game'; import { Player } from './state'; -import { Strategy } from './strategy'; +import { StrategyName } from './strategy'; export interface GameConfig { heapSize: number; minTokensToRemove: number; maxTokensToRemove: number; startingPlayer: Player; - strategy: Strategy; + strategy: StrategyName; } export declare function getStateFromConfig(): GameFn; diff --git a/dist/src/lib/game.js b/dist/src/lib/game.js index e268e75..63695e8 100644 --- a/dist/src/lib/game.js +++ b/dist/src/lib/game.js @@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); const lodash_1 = require("lodash"); const predicates_1 = require("./predicates"); const state_1 = require("./state"); +const strategy_1 = require("./strategy"); const util_1 = require("./util"); function playHumanTurn(tokensToRemove) { return playTurn(state_1.Player.Human, tokensToRemove); @@ -15,7 +16,7 @@ function playMachineTurn() { } exports.playMachineTurn = playMachineTurn; function getNextTurn(gameState) { - return gameState.config.strategy.getNextTurn(gameState); + return strategy_1.getStrategy(gameState.config.strategy)(gameState); } function playTurn(player, tokensToRemove) { return lodash_1.flow(util_1.abortIf(predicates_1.isInvalidTurn(tokensToRemove), ({ minTokensAllowedToRemove, maxTokensAllowedToRemove }) => `You may remove between ${minTokensAllowedToRemove} and ${maxTokensAllowedToRemove} tokens from the heap. You tried to remove ${tokensToRemove} tokens.`), util_1.abortIf(predicates_1.isFinished(), ({ winner }) => `The game has already ended. ${winner} is the winner.`), state_1.updateStateWithTurn(player, tokensToRemove)); diff --git a/dist/src/lib/strategy.d.ts b/dist/src/lib/strategy.d.ts index b7fd7b2..606a9d1 100644 --- a/dist/src/lib/strategy.d.ts +++ b/dist/src/lib/strategy.d.ts @@ -3,10 +3,17 @@ export * from './strategy/always-min'; export * from './strategy/mimic-human'; export * from './strategy/random'; export * from './strategy/remainder'; -export declare type StrategyFn = (gameState: GameState) => number; -export interface Strategy { - name: string; - getNextTurn: StrategyFn; +export declare type Strategy = (gameState: GameState) => number; +export declare enum StrategyName { + AlwaysMinStrategy = "alwaysMinStrategy", + MimicHumanStrategy = "mimicHumanStrategy", + RandomStrategy = "randomStrategy", + RemainderStrategy = "remainderStrategy", } -export declare type StrategyFactory = (...args: any[]) => Strategy; -export declare function getStrategies(): StrategyFactory[]; +export declare const strategies: { + [StrategyName.AlwaysMinStrategy]: Strategy; + [StrategyName.MimicHumanStrategy]: Strategy; + [StrategyName.RandomStrategy]: Strategy; + [StrategyName.RemainderStrategy]: Strategy; +}; +export declare function getStrategy(strategyName: StrategyName): Strategy; diff --git a/dist/src/lib/strategy.js b/dist/src/lib/strategy.js index 659ddcb..fea58ff 100644 --- a/dist/src/lib/strategy.js +++ b/dist/src/lib/strategy.js @@ -3,6 +3,8 @@ function __export(m) { for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; } Object.defineProperty(exports, "__esModule", { value: true }); +const lodash_1 = require("lodash"); +const util_1 = require("util"); const always_min_1 = require("./strategy/always-min"); const mimic_human_1 = require("./strategy/mimic-human"); const random_1 = require("./strategy/random"); @@ -11,12 +13,24 @@ __export(require("./strategy/always-min")); __export(require("./strategy/mimic-human")); __export(require("./strategy/random")); __export(require("./strategy/remainder")); -function getStrategies() { - return [ - always_min_1.alwaysMinStrategy, - mimic_human_1.mimicHumanStrategy, - random_1.randomStrategy, - remainder_1.remainderStrategy - ]; +var StrategyName; +(function (StrategyName) { + StrategyName["AlwaysMinStrategy"] = "alwaysMinStrategy"; + StrategyName["MimicHumanStrategy"] = "mimicHumanStrategy"; + StrategyName["RandomStrategy"] = "randomStrategy"; + StrategyName["RemainderStrategy"] = "remainderStrategy"; +})(StrategyName = exports.StrategyName || (exports.StrategyName = {})); +exports.strategies = { + [StrategyName.AlwaysMinStrategy]: always_min_1.alwaysMinStrategy, + [StrategyName.MimicHumanStrategy]: mimic_human_1.mimicHumanStrategy, + [StrategyName.RandomStrategy]: random_1.randomStrategy, + [StrategyName.RemainderStrategy]: remainder_1.remainderStrategy +}; +function getStrategy(strategyName) { + const strategy = lodash_1.find(exports.strategies, (fn, name) => name === strategyName); + if (util_1.isUndefined(strategy)) { + throw new Error(`${strategyName} is not a valid Strategy.`); + } + return strategy; } -exports.getStrategies = getStrategies; +exports.getStrategy = getStrategy; diff --git a/dist/src/lib/strategy/always-min.d.ts b/dist/src/lib/strategy/always-min.d.ts index e3932c3..7d836d7 100644 --- a/dist/src/lib/strategy/always-min.d.ts +++ b/dist/src/lib/strategy/always-min.d.ts @@ -1,2 +1,2 @@ -import { StrategyFactory } from '../strategy'; -export declare const alwaysMinStrategy: StrategyFactory; +import { Strategy } from '../strategy'; +export declare const alwaysMinStrategy: Strategy; diff --git a/dist/src/lib/strategy/always-min.js b/dist/src/lib/strategy/always-min.js index f669296..8321a8d 100644 --- a/dist/src/lib/strategy/always-min.js +++ b/dist/src/lib/strategy/always-min.js @@ -1,8 +1,5 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.alwaysMinStrategy = () => ({ - name: 'alwaysMinStrategy', - getNextTurn({ minTokensAllowedToRemove }) { - return minTokensAllowedToRemove; - } -}); +exports.alwaysMinStrategy = ({ minTokensAllowedToRemove }) => { + return minTokensAllowedToRemove; +}; diff --git a/dist/src/lib/strategy/mimic-human.d.ts b/dist/src/lib/strategy/mimic-human.d.ts index 60e5f6e..f1d4246 100644 --- a/dist/src/lib/strategy/mimic-human.d.ts +++ b/dist/src/lib/strategy/mimic-human.d.ts @@ -1,2 +1,2 @@ -import { StrategyFactory } from '../strategy'; -export declare const mimicHumanStrategy: StrategyFactory; +import { Strategy } from '../strategy'; +export declare const mimicHumanStrategy: Strategy; diff --git a/dist/src/lib/strategy/mimic-human.js b/dist/src/lib/strategy/mimic-human.js index bb5e47c..5cc8ec5 100644 --- a/dist/src/lib/strategy/mimic-human.js +++ b/dist/src/lib/strategy/mimic-human.js @@ -1,12 +1,9 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const lodash_1 = require("lodash"); -exports.mimicHumanStrategy = () => ({ - name: 'mimicHumanStrategy', - getNextTurn({ turns, minTokensAllowedToRemove, maxTokensAllowedToRemove }) { - if (lodash_1.isEmpty(turns)) { - return minTokensAllowedToRemove; - } - return Math.min(lodash_1.last(turns).tokensRemoved, maxTokensAllowedToRemove); +exports.mimicHumanStrategy = ({ turns, minTokensAllowedToRemove, maxTokensAllowedToRemove }) => { + if (lodash_1.isEmpty(turns)) { + return minTokensAllowedToRemove; } -}); + return Math.min(lodash_1.last(turns).tokensRemoved, maxTokensAllowedToRemove); +}; diff --git a/dist/src/lib/strategy/random.d.ts b/dist/src/lib/strategy/random.d.ts index b727aad..3a8456b 100644 --- a/dist/src/lib/strategy/random.d.ts +++ b/dist/src/lib/strategy/random.d.ts @@ -1,2 +1,2 @@ -import { StrategyFactory } from '../strategy'; -export declare const randomStrategy: StrategyFactory; +import { Strategy } from '../strategy'; +export declare const randomStrategy: Strategy; diff --git a/dist/src/lib/strategy/random.js b/dist/src/lib/strategy/random.js index 791839d..5005425 100644 --- a/dist/src/lib/strategy/random.js +++ b/dist/src/lib/strategy/random.js @@ -1,9 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const lodash_1 = require("lodash"); -exports.randomStrategy = () => ({ - name: 'randomStrategy', - getNextTurn({ minTokensAllowedToRemove, maxTokensAllowedToRemove }) { - return lodash_1.random(minTokensAllowedToRemove, maxTokensAllowedToRemove); - } -}); +exports.randomStrategy = ({ minTokensAllowedToRemove, maxTokensAllowedToRemove }) => { + return lodash_1.random(minTokensAllowedToRemove, maxTokensAllowedToRemove); +}; diff --git a/dist/src/lib/strategy/remainder.d.ts b/dist/src/lib/strategy/remainder.d.ts index 630ced6..3737ed3 100644 --- a/dist/src/lib/strategy/remainder.d.ts +++ b/dist/src/lib/strategy/remainder.d.ts @@ -1,2 +1,2 @@ -import { StrategyFactory } from '../strategy'; -export declare const remainderStrategy: StrategyFactory; +import { Strategy } from '../strategy'; +export declare const remainderStrategy: Strategy; diff --git a/dist/src/lib/strategy/remainder.js b/dist/src/lib/strategy/remainder.js index 244e6d2..60b40d9 100644 --- a/dist/src/lib/strategy/remainder.js +++ b/dist/src/lib/strategy/remainder.js @@ -1,25 +1,22 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.remainderStrategy = () => ({ - name: 'remainderStrategy', - getNextTurn({ heapSize, minTokensAllowedToRemove, maxTokensAllowedToRemove }) { - let tokensToRemove = 0; - // build groups of (min + maxTokensToRemove) and determine the number - // of remaining tokens - const remainder = heapSize % (minTokensAllowedToRemove + maxTokensAllowedToRemove); - // to reduce the number of remaining tokens to one, remove all of the remaining - // tokens but the minimum allowed number of tokens to remove - if (remainder > 0) { - tokensToRemove = remainder - minTokensAllowedToRemove; - } - // in case there are no remaining tokens, remove enough tokens to leave - // one remaining token besides the groups of (min + maxTokensToRemove) - if (remainder === 0) { - tokensToRemove = maxTokensAllowedToRemove; - } - // when the current heap size already consists of groups of (min + maxTokensToRemove) - // and minTokensToRemove additional remaining tokens, the amount of tokens to remove - // would be 0; in this case remove the minimum allowed number of tokens - return Math.max(tokensToRemove, minTokensAllowedToRemove); +exports.remainderStrategy = ({ heapSize, minTokensAllowedToRemove, maxTokensAllowedToRemove }) => { + let tokensToRemove = 0; + // build groups of (min + maxTokensToRemove) and determine the number + // of remaining tokens + const remainder = heapSize % (minTokensAllowedToRemove + maxTokensAllowedToRemove); + // to reduce the number of remaining tokens to one, remove all of the remaining + // tokens but the minimum allowed number of tokens to remove + if (remainder > 0) { + tokensToRemove = remainder - minTokensAllowedToRemove; } -}); + // in case there are no remaining tokens, remove enough tokens to leave + // one remaining token besides the groups of (min + maxTokensToRemove) + if (remainder === 0) { + tokensToRemove = maxTokensAllowedToRemove; + } + // when the current heap size already consists of groups of (min + maxTokensToRemove) + // and minTokensToRemove additional remaining tokens, the amount of tokens to remove + // would be 0; in this case remove the minimum allowed number of tokens + return Math.max(tokensToRemove, minTokensAllowedToRemove); +}; diff --git a/src/lib/config.ts b/src/lib/config.ts index abb7b12..2144caa 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -1,13 +1,13 @@ import { GameFn } from './game'; import { Player } from './state'; -import { Strategy } from './strategy'; +import { StrategyName } from './strategy'; export interface GameConfig { heapSize: number; minTokensToRemove: number; maxTokensToRemove: number; startingPlayer: Player; - strategy: Strategy; + strategy: StrategyName; } export function getStateFromConfig(): GameFn { diff --git a/src/lib/game.ts b/src/lib/game.ts index 3d51559..462ece9 100644 --- a/src/lib/game.ts +++ b/src/lib/game.ts @@ -1,6 +1,7 @@ import { flow } from 'lodash'; import { isFinished, isInvalidTurn } from './predicates'; import { GameState, Player, updateStateWithTurn } from './state'; +import { getStrategy } from './strategy'; import { abortIf } from './util'; export type GameFn = (arg: T) => GameState; @@ -19,7 +20,7 @@ export function playMachineTurn(): GameFn { } function getNextTurn(gameState: GameState): number { - return gameState.config.strategy.getNextTurn(gameState); + return getStrategy(gameState.config.strategy)(gameState); } function playTurn(player: Player, tokensToRemove: number): GameFn { diff --git a/src/lib/strategy.ts b/src/lib/strategy.ts index 41fe3c5..08194ad 100644 --- a/src/lib/strategy.ts +++ b/src/lib/strategy.ts @@ -1,3 +1,5 @@ +import { find } from 'lodash'; +import { isUndefined } from 'util'; import { GameState } from './state'; import { alwaysMinStrategy } from './strategy/always-min'; import { mimicHumanStrategy } from './strategy/mimic-human'; @@ -9,21 +11,28 @@ export * from './strategy/mimic-human'; export * from './strategy/random'; export * from './strategy/remainder'; -export type StrategyFn = (gameState: GameState) => number; +export type Strategy = (gameState: GameState) => number; -export interface Strategy { - name: string; - getNextTurn: StrategyFn; +export enum StrategyName { + AlwaysMinStrategy = 'alwaysMinStrategy', + MimicHumanStrategy = 'mimicHumanStrategy', + RandomStrategy = 'randomStrategy', + RemainderStrategy = 'remainderStrategy' } -export type StrategyFactory = (...args: any[]) => Strategy; +export const strategies = { + [StrategyName.AlwaysMinStrategy]: alwaysMinStrategy, + [StrategyName.MimicHumanStrategy]: mimicHumanStrategy, + [StrategyName.RandomStrategy]: randomStrategy, + [StrategyName.RemainderStrategy]: remainderStrategy +}; +export function getStrategy(strategyName: StrategyName): Strategy { + const strategy = find(strategies, (fn, name) => name === strategyName)!; -export function getStrategies(): StrategyFactory[] { - return [ - alwaysMinStrategy, - mimicHumanStrategy, - randomStrategy, - remainderStrategy - ]; + if (isUndefined(strategy)) { + throw new Error(`${strategyName} is not a valid Strategy.`); + } + + return strategy; } diff --git a/src/lib/strategy/always-min.ts b/src/lib/strategy/always-min.ts index 8311b18..95dd2c3 100644 --- a/src/lib/strategy/always-min.ts +++ b/src/lib/strategy/always-min.ts @@ -1,8 +1,5 @@ -import { StrategyFactory } from '../strategy'; +import { Strategy } from '../strategy'; -export const alwaysMinStrategy: StrategyFactory = () => ({ - name: 'alwaysMinStrategy', - getNextTurn({ minTokensAllowedToRemove }): number { - return minTokensAllowedToRemove; - } -}); +export const alwaysMinStrategy: Strategy = ({ minTokensAllowedToRemove }): number => { + return minTokensAllowedToRemove; +}; diff --git a/src/lib/strategy/mimic-human.ts b/src/lib/strategy/mimic-human.ts index 2569a7f..d7f9684 100644 --- a/src/lib/strategy/mimic-human.ts +++ b/src/lib/strategy/mimic-human.ts @@ -1,16 +1,13 @@ import { isEmpty, last } from 'lodash'; -import { StrategyFactory } from '../strategy'; +import { Strategy } from '../strategy'; -export const mimicHumanStrategy: StrategyFactory = () => ({ - name: 'mimicHumanStrategy', - getNextTurn({ turns, minTokensAllowedToRemove, maxTokensAllowedToRemove }): number { - if (isEmpty(turns)) { - return minTokensAllowedToRemove; - } - - return Math.min( - last(turns)!.tokensRemoved, - maxTokensAllowedToRemove - ); +export const mimicHumanStrategy: Strategy = ({ turns, minTokensAllowedToRemove, maxTokensAllowedToRemove }): number => { + if (isEmpty(turns)) { + return minTokensAllowedToRemove; } -}); + + return Math.min( + last(turns)!.tokensRemoved, + maxTokensAllowedToRemove + ); +}; diff --git a/src/lib/strategy/random.ts b/src/lib/strategy/random.ts index 15ae249..2842967 100644 --- a/src/lib/strategy/random.ts +++ b/src/lib/strategy/random.ts @@ -1,12 +1,9 @@ import { random } from 'lodash'; -import { StrategyFactory } from '../strategy'; +import { Strategy } from '../strategy'; -export const randomStrategy: StrategyFactory = () => ({ - name: 'randomStrategy', - getNextTurn({ minTokensAllowedToRemove, maxTokensAllowedToRemove }): number { - return random( - minTokensAllowedToRemove, - maxTokensAllowedToRemove - ); - } -}); +export const randomStrategy: Strategy = ({ minTokensAllowedToRemove, maxTokensAllowedToRemove }): number => { + return random( + minTokensAllowedToRemove, + maxTokensAllowedToRemove + ); +}; diff --git a/src/lib/strategy/remainder.ts b/src/lib/strategy/remainder.ts index d769836..82cc335 100644 --- a/src/lib/strategy/remainder.ts +++ b/src/lib/strategy/remainder.ts @@ -1,29 +1,26 @@ -import { StrategyFactory } from '../strategy'; +import { Strategy } from '../strategy'; -export const remainderStrategy: StrategyFactory = () => ({ - name: 'remainderStrategy', - getNextTurn({ heapSize, minTokensAllowedToRemove, maxTokensAllowedToRemove }): number { - let tokensToRemove = 0; +export const remainderStrategy: Strategy = ({ heapSize, minTokensAllowedToRemove, maxTokensAllowedToRemove }): number => { + let tokensToRemove = 0; - // build groups of (min + maxTokensToRemove) and determine the number - // of remaining tokens - const remainder = heapSize % (minTokensAllowedToRemove + maxTokensAllowedToRemove); + // build groups of (min + maxTokensToRemove) and determine the number + // of remaining tokens + const remainder = heapSize % (minTokensAllowedToRemove + maxTokensAllowedToRemove); - // to reduce the number of remaining tokens to one, remove all of the remaining - // tokens but the minimum allowed number of tokens to remove - if (remainder > 0) { - tokensToRemove = remainder - minTokensAllowedToRemove; - } - - // in case there are no remaining tokens, remove enough tokens to leave - // one remaining token besides the groups of (min + maxTokensToRemove) - if (remainder === 0) { - tokensToRemove = maxTokensAllowedToRemove; - } + // to reduce the number of remaining tokens to one, remove all of the remaining + // tokens but the minimum allowed number of tokens to remove + if (remainder > 0) { + tokensToRemove = remainder - minTokensAllowedToRemove; + } - // when the current heap size already consists of groups of (min + maxTokensToRemove) - // and minTokensToRemove additional remaining tokens, the amount of tokens to remove - // would be 0; in this case remove the minimum allowed number of tokens - return Math.max(tokensToRemove, minTokensAllowedToRemove); + // in case there are no remaining tokens, remove enough tokens to leave + // one remaining token besides the groups of (min + maxTokensToRemove) + if (remainder === 0) { + tokensToRemove = maxTokensAllowedToRemove; } -}); + + // when the current heap size already consists of groups of (min + maxTokensToRemove) + // and minTokensToRemove additional remaining tokens, the amount of tokens to remove + // would be 0; in this case remove the minimum allowed number of tokens + return Math.max(tokensToRemove, minTokensAllowedToRemove); +}; diff --git a/test/lib/game.test.ts b/test/lib/game.test.ts index c6613b0..730df06 100644 --- a/test/lib/game.test.ts +++ b/test/lib/game.test.ts @@ -1,7 +1,7 @@ import { first, range } from 'lodash'; -import { GameState, Player, Strategy } from '../../index'; +import { GameState, Player, Strategy, strategies } from '../../index'; import { playHumanTurn, playMachineTurn } from '../../src/lib/game'; -import { getMockState, getMockStrategy } from '../util'; +import { getMockState, getMockStrategy, mockStrategyName } from '../util'; const initialState = getMockState(); @@ -35,18 +35,24 @@ describe('playHumanTurn', () => { }); describe('playMachineTurn', () => { - let mockStrategy: Strategy; - let getNextTurn: jest.SpyInstance; + const mockStrategy: jest.SpyInstance = ( getMockStrategy()); let stateWithMockStrategy: GameState; + beforeAll(() => { + strategies[mockStrategyName] = mockStrategy; + }); + + afterAll(() => { + delete strategies[mockStrategyName]; + }); + beforeEach(() => { - mockStrategy = getMockStrategy(); - getNextTurn = ( mockStrategy.getNextTurn); + mockStrategy.mockClear(); stateWithMockStrategy = { ...initialState, config: { ...initialState.config, - strategy: mockStrategy + strategy: ( mockStrategyName) } }; }); @@ -76,6 +82,6 @@ describe('playMachineTurn', () => { test('it uses the configured strategy to determine the number of tokens to remove', () => { const state = playMachineTurn()(stateWithMockStrategy); - expect(getNextTurn).toHaveBeenLastCalledWith(stateWithMockStrategy); + expect(mockStrategy).toHaveBeenLastCalledWith(stateWithMockStrategy); }); }); diff --git a/test/lib/strategy/always-min.test.ts b/test/lib/strategy/always-min.test.ts index 61796fb..e833016 100644 --- a/test/lib/strategy/always-min.test.ts +++ b/test/lib/strategy/always-min.test.ts @@ -1,16 +1,10 @@ import { Strategy, alwaysMinStrategy } from '../../../index'; import { getMockState } from '../../util'; -describe('AlwaysMinStrategy', () => { - let strategy: Strategy; - +describe('alwaysMinStrategy', () => { const gameState = getMockState(); - beforeEach(() => { - strategy = alwaysMinStrategy(); - }); - test('it always removes the minimum amount of tokens allowed to remove', () => { - expect(strategy.getNextTurn(gameState)).toBe(gameState.minTokensAllowedToRemove); + expect(alwaysMinStrategy(gameState)).toBe(gameState.minTokensAllowedToRemove); }); }); diff --git a/test/lib/strategy/mimic-human.test.ts b/test/lib/strategy/mimic-human.test.ts index 2a4a99d..4f797bb 100644 --- a/test/lib/strategy/mimic-human.test.ts +++ b/test/lib/strategy/mimic-human.test.ts @@ -2,16 +2,10 @@ import { range } from 'lodash'; import { Player, Strategy, Turn, mimicHumanStrategy } from '../../../index'; import { getMockState } from '../../util'; -describe('MimicHumanStrategy', () => { - let strategy: Strategy; - +describe('mimicHumanStrategy', () => { const gameState = getMockState(); const gameConfig = gameState.config; - beforeEach(() => { - strategy = mimicHumanStrategy(); - }); - test('it removes the same amount of tokens as the player did before', () => { range(gameConfig.minTokensToRemove, gameConfig.maxTokensToRemove) .forEach(tokensRemoved => { @@ -23,7 +17,7 @@ describe('MimicHumanStrategy', () => { }] }; - expect(strategy.getNextTurn(state)).toBe(tokensRemoved); + expect(mimicHumanStrategy(state)).toBe(tokensRemoved); }); }); @@ -40,6 +34,6 @@ describe('MimicHumanStrategy', () => { }] }; - expect(strategy.getNextTurn(state)).toBe(heapSize); + expect(mimicHumanStrategy(state)).toBe(heapSize); }); }); diff --git a/test/lib/strategy/random.test.ts b/test/lib/strategy/random.test.ts index 68d00c0..83209ae 100644 --- a/test/lib/strategy/random.test.ts +++ b/test/lib/strategy/random.test.ts @@ -2,18 +2,12 @@ import { Strategy, randomStrategy } from '../../../index'; import { getMockState } from '../../util'; describe('RandomStrategy', () => { - let strategy: Strategy; - const gameState = getMockState(); const gameConfig = gameState.config; - beforeEach(() => { - strategy = randomStrategy(); - }); - test(`it randomly removes between ${gameConfig.minTokensToRemove} and ${gameConfig.maxTokensToRemove} tokens`, () => { - expect(strategy.getNextTurn(gameState)).toBeGreaterThanOrEqual(gameConfig.minTokensToRemove); - expect(strategy.getNextTurn(gameState)).toBeLessThanOrEqual(gameConfig.maxTokensToRemove); + expect(randomStrategy(gameState)).toBeGreaterThanOrEqual(gameConfig.minTokensToRemove); + expect(randomStrategy(gameState)).toBeLessThanOrEqual(gameConfig.maxTokensToRemove); }); test(`it does not try to remove more tokens than left on the heap`, () => { @@ -24,6 +18,6 @@ describe('RandomStrategy', () => { maxTokensAllowedToRemove: gameConfig.minTokensToRemove }; - expect(strategy.getNextTurn(state)).toBe(gameConfig.minTokensToRemove); + expect(randomStrategy(state)).toBe(gameConfig.minTokensToRemove); }); }); diff --git a/test/lib/strategy/remainder.test.ts b/test/lib/strategy/remainder.test.ts index af88e86..4e34307 100644 --- a/test/lib/strategy/remainder.test.ts +++ b/test/lib/strategy/remainder.test.ts @@ -3,17 +3,11 @@ import { Strategy, remainderStrategy } from '../../../index'; import { getMockState } from '../../util'; describe('RemainderStrategy', () => { - let strategy: Strategy; - const gameState = getMockState(); const gameConfig = gameState.config; const groupSize = gameConfig.minTokensToRemove + gameConfig.maxTokensToRemove; - beforeEach(() => { - strategy = remainderStrategy(); - }); - describe('try to get the heap size to (n * (MIN + MAX)) + MIN;', () => { range(0, groupSize) @@ -36,7 +30,7 @@ describe('RemainderStrategy', () => { ...gameState, heapSize }; - expect(strategy.getNextTurn(state)).toBe(numberOfTokensToRemove); + expect(remainderStrategy(state)).toBe(numberOfTokensToRemove); }); }); }); diff --git a/test/nim.test.ts b/test/nim.test.ts index b1667ff..5648bde 100644 --- a/test/nim.test.ts +++ b/test/nim.test.ts @@ -1,14 +1,13 @@ -import { findLast, first, flow, last, range } from 'lodash'; -import { GameConfig, GameState, Player, Strategy, getStrategies, playRound, startGame } from '../index'; -import { getMockConfig, getMockStrategy, playGame } from './util'; +import { findLast, first, flow, forEach, last, range } from 'lodash'; +import { GameConfig, GameState, Player, Strategy, StrategyName, playRound, startGame, strategies } from '../index'; +import { getMockConfig, getMockStrategy, mockStrategyName, playGame } from './util'; const gameConfig = getMockConfig(); // tslint:disable-next-line:variable-name -getStrategies().forEach(strategyFactory => { - const strategy = strategyFactory(); +Object.keys(strategies).forEach((strategy: StrategyName) => { - describe(`NimGame with ${strategy.name}`, () => { + describe(`NimGame with ${strategy}`, () => { test('the initial heap size is configurable', () => { const gameState = startGame({ ...gameConfig, @@ -130,23 +129,29 @@ getStrategies().forEach(strategyFactory => { }); describe('machine strategy', () => { - let mockStrategy; - let getNextTurn: jest.SpyInstance; + const mockStrategy: jest.SpyInstance = ( getMockStrategy()); + + beforeAll(() => { + strategies[mockStrategyName] = mockStrategy; + }); + + afterAll(() => { + delete strategies[mockStrategyName]; + }); beforeEach(() => { - mockStrategy = getMockStrategy(); - getNextTurn = ( mockStrategy.getNextTurn); + mockStrategy.mockClear(); }); test('when the machine is the starting player, it receives the current game state to make its decision', () => { const config = { ...gameConfig, startingPlayer: Player.Machine, - strategy: mockStrategy + strategy: ( mockStrategyName) }; startGame(config); - const passedGameState: GameState = getNextTurn.mock.calls[0][0]; + const passedGameState: GameState = mockStrategy.mock.calls[0][0]; expect(passedGameState.config).toEqual(config); expect(passedGameState.heapSize).toBe(config.heapSize); @@ -161,7 +166,7 @@ describe('machine strategy', () => { const config = { ...gameConfig, startingPlayer: Player.Human, - strategy: mockStrategy + strategy: ( mockStrategyName) }; const gameState = flow( startGame, @@ -170,7 +175,7 @@ describe('machine strategy', () => { const heapSizeAfterHumanTurn = config.heapSize - tokensToRemove; - const passedGameState: GameState = getNextTurn.mock.calls[0][0]; + const passedGameState: GameState = mockStrategy.mock.calls[0][0]; expect(passedGameState.config).toEqual(config); expect(passedGameState.heapSize).toBe(heapSizeAfterHumanTurn); diff --git a/test/strategy.test.ts b/test/strategy.test.ts index 0acf90f..5507545 100644 --- a/test/strategy.test.ts +++ b/test/strategy.test.ts @@ -1,12 +1,16 @@ -import { alwaysMinStrategy, getStrategies, mimicHumanStrategy, randomStrategy, remainderStrategy } from '../index'; - -describe('getStrategies', () => { - test('it returns all strategies', () => { - expect(getStrategies()).toEqual([ - alwaysMinStrategy, - mimicHumanStrategy, - randomStrategy, - remainderStrategy - ]); +import { forEach } from 'lodash'; +import { Strategy, StrategyName, getStrategy, strategies } from '../index'; + +describe('getStrategy', () => { + + test('it returns the strategy matching the given name', () => { + forEach(strategies, (strategy: Strategy, name: StrategyName) => { + expect(getStrategy(name)).toBe(strategy); + }); }); + + test('it returns undefined when to strategy matches the given name', () => { + expect(() => getStrategy( 'invalidName')).toThrowError(); + }); + }); diff --git a/test/util.ts b/test/util.ts index c642b6c..129909b 100644 --- a/test/util.ts +++ b/test/util.ts @@ -1,5 +1,5 @@ import { flow, negate, random } from 'lodash'; -import { GameConfig, GameState, Player, StrategyFactory, isFinished, playRound } from '../index'; +import { GameConfig, GameState, Player, Strategy, StrategyName, isFinished, playRound } from '../index'; import { GameFn } from '../src/lib/game'; import { when } from '../src/lib/util'; @@ -10,19 +10,20 @@ export const playGame = (): GameFn => { )(gameState); }; -export const getMockStrategy: StrategyFactory = () => ({ - name: 'mockStrategy', - getNextTurn: jest.fn( +export const mockStrategyName = 'mockStrategy'; + +export const getMockStrategy: () => Strategy = () => { + return jest.fn( (gameState: GameState) => random(gameState.minTokensAllowedToRemove, gameState.maxTokensAllowedToRemove) - ) -}); + ); +}; export const getMockConfig: () => GameConfig = () => ({ heapSize: 13, minTokensToRemove: 1, maxTokensToRemove: 3, startingPlayer: Player.Human, - strategy: getMockStrategy() + strategy: StrategyName.RandomStrategy }); export const getMockState: () => GameState = () => {