Skip to content

Commit

Permalink
feat: simplified Strategy API, GameConfig now serializable
Browse files Browse the repository at this point in the history
BREAKING CHANGE: GameConfig.strategy is now the strategy name
  • Loading branch information
Marvin Luchs committed Dec 30, 2017
1 parent 5b6b087 commit c5cad60
Show file tree
Hide file tree
Showing 27 changed files with 208 additions and 208 deletions.
4 changes: 2 additions & 2 deletions dist/src/lib/config.d.ts
Original file line number Diff line number Diff line change
@@ -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<GameConfig>;
3 changes: 2 additions & 1 deletion dist/src/lib/game.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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));
Expand Down
19 changes: 13 additions & 6 deletions dist/src/lib/strategy.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
30 changes: 22 additions & 8 deletions dist/src/lib/strategy.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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;
4 changes: 2 additions & 2 deletions dist/src/lib/strategy/always-min.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
import { StrategyFactory } from '../strategy';
export declare const alwaysMinStrategy: StrategyFactory;
import { Strategy } from '../strategy';
export declare const alwaysMinStrategy: Strategy;
9 changes: 3 additions & 6 deletions dist/src/lib/strategy/always-min.js
Original file line number Diff line number Diff line change
@@ -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;
};
4 changes: 2 additions & 2 deletions dist/src/lib/strategy/mimic-human.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
import { StrategyFactory } from '../strategy';
export declare const mimicHumanStrategy: StrategyFactory;
import { Strategy } from '../strategy';
export declare const mimicHumanStrategy: Strategy;
13 changes: 5 additions & 8 deletions dist/src/lib/strategy/mimic-human.js
Original file line number Diff line number Diff line change
@@ -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);
};
4 changes: 2 additions & 2 deletions dist/src/lib/strategy/random.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
import { StrategyFactory } from '../strategy';
export declare const randomStrategy: StrategyFactory;
import { Strategy } from '../strategy';
export declare const randomStrategy: Strategy;
9 changes: 3 additions & 6 deletions dist/src/lib/strategy/random.js
Original file line number Diff line number Diff line change
@@ -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);
};
4 changes: 2 additions & 2 deletions dist/src/lib/strategy/remainder.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
import { StrategyFactory } from '../strategy';
export declare const remainderStrategy: StrategyFactory;
import { Strategy } from '../strategy';
export declare const remainderStrategy: Strategy;
41 changes: 19 additions & 22 deletions dist/src/lib/strategy/remainder.js
Original file line number Diff line number Diff line change
@@ -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);
};
4 changes: 2 additions & 2 deletions src/lib/config.ts
Original file line number Diff line number Diff line change
@@ -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<GameConfig> {
Expand Down
3 changes: 2 additions & 1 deletion src/lib/game.ts
Original file line number Diff line number Diff line change
@@ -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<T = GameState> = (arg: T) => GameState;
Expand All @@ -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 {
Expand Down
33 changes: 21 additions & 12 deletions src/lib/strategy.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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;
}
11 changes: 4 additions & 7 deletions src/lib/strategy/always-min.ts
Original file line number Diff line number Diff line change
@@ -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;
};
23 changes: 10 additions & 13 deletions src/lib/strategy/mimic-human.ts
Original file line number Diff line number Diff line change
@@ -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
);
};
17 changes: 7 additions & 10 deletions src/lib/strategy/random.ts
Original file line number Diff line number Diff line change
@@ -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
);
};
45 changes: 21 additions & 24 deletions src/lib/strategy/remainder.ts
Original file line number Diff line number Diff line change
@@ -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);
};
Loading

0 comments on commit c5cad60

Please sign in to comment.