From ba27eddd558b925c12a20daf7eb4679a63648733 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Lemaire?= Date: Tue, 30 Dec 2014 01:00:00 +0100 Subject: [PATCH] Added unit testing with jasmine + battle play order by initiative throws --- buildout.cfg | 1 + src/index.html | 34 ++- src/scripts/definitions/jasmine.d.ts | 429 +++++++++++++++++++++++++++ src/scripts/game/Battle.ts | 21 +- src/scripts/game/Fleet.ts | 7 + src/scripts/game/RandomGenerator.ts | 29 ++ src/scripts/game/Ship.ts | 29 +- src/scripts/specs/Battle.spec.ts | 30 ++ 8 files changed, 565 insertions(+), 15 deletions(-) create mode 100644 src/scripts/definitions/jasmine.d.ts create mode 100644 src/scripts/game/RandomGenerator.ts create mode 100644 src/scripts/specs/Battle.spec.ts diff --git a/buildout.cfg b/buildout.cfg index 95d18f7..17b8591 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -63,6 +63,7 @@ base-directory = ${buildout:directory}/src/vendor downloads = . packages = phaser-official + jasmine [mkdir-var] recipe = z3c.recipe.mkdir diff --git a/src/index.html b/src/index.html index f80944d..7c39d0d 100644 --- a/src/index.html +++ b/src/index.html @@ -5,25 +5,35 @@ SpaceTac + -
+
- - - + + + - - - - + + + + + + + - + }; + \ No newline at end of file diff --git a/src/scripts/definitions/jasmine.d.ts b/src/scripts/definitions/jasmine.d.ts new file mode 100644 index 0000000..0ade8ee --- /dev/null +++ b/src/scripts/definitions/jasmine.d.ts @@ -0,0 +1,429 @@ +// Type definitions for Jasmine 2.0 +// Project: http://pivotal.github.com/jasmine/ +// Definitions by: Boris Yankov , Theodore Brown +// Definitions: https://github.com/borisyankov/DefinitelyTyped + + +// For ddescribe / iit use : https://github.com/borisyankov/DefinitelyTyped/blob/master/karma-jasmine/karma-jasmine.d.ts + +declare function describe(description: string, specDefinitions: () => void): void; +// declare function ddescribe(description: string, specDefinitions: () => void): void; Not a part of jasmine. Angular team adds these +declare function xdescribe(description: string, specDefinitions: () => void): void; + +declare function it(expectation: string, assertion?: () => void): void; +declare function it(expectation: string, assertion?: (done: () => void) => void): void; +// declare function iit(expectation: string, assertion?: () => void): void; Not a part of jasmine. Angular team adds these +// declare function iit(expectation: string, assertion?: (done: () => void) => void): void; Not a part of jasmine. Angular team adds these +declare function xit(expectation: string, assertion?: () => void): void; +declare function xit(expectation: string, assertion?: (done: () => void) => void): void; + +/** If you call the function pending anywhere in the spec body, no matter the expectations, the spec will be marked pending. */ +declare function pending(): void; + +declare function beforeEach(action: () => void): void; +declare function beforeEach(action: (done: () => void) => void): void; +declare function afterEach(action: () => void): void; +declare function afterEach(action: (done: () => void) => void): void; + +declare function expect(spy: Function): jasmine.Matchers; +declare function expect(actual: any): jasmine.Matchers; + +declare function spyOn(object: any, method: string): jasmine.Spy; + +declare function runs(asyncMethod: Function): void; +declare function waitsFor(latchMethod: () => boolean, failureMessage?: string, timeout?: number): void; +declare function waits(timeout?: number): void; + +declare module jasmine { + + var clock: () => Clock; + + function any(aclass: any): Any; + function objectContaining(sample: any): ObjectContaining; + function createSpy(name: string, originalFn?: Function): Spy; + function createSpyObj(baseName: string, methodNames: any[]): any; + function createSpyObj(baseName: string, methodNames: any[]): T; + function pp(value: any): string; + function getEnv(): Env; + function addMatchers(matchers: any): Any; + + interface Any { + + new (expectedClass: any): any; + + jasmineMatches(other: any): boolean; + jasmineToString(): string; + } + + interface ObjectContaining { + new (sample: any): any; + + jasmineMatches(other: any, mismatchKeys: any[], mismatchValues: any[]): boolean; + jasmineToString(): string; + } + + interface Block { + + new (env: Env, func: SpecFunction, spec: Spec): any; + + execute(onComplete: () => void): void; + } + + interface WaitsBlock extends Block { + new (env: Env, timeout: number, spec: Spec): any; + } + + interface WaitsForBlock extends Block { + new (env: Env, timeout: number, latchFunction: SpecFunction, message: string, spec: Spec): any; + } + + interface Clock { + install(): void; + uninstall(): void; + /** Calls to any registered callback are triggered when the clock is ticked forward via the jasmine.clock().tick function, which takes a number of milliseconds. */ + tick(ms: number): void; + } + + interface Env { + setTimeout: any; + clearTimeout: void; + setInterval: any; + clearInterval: void; + updateInterval: number; + + currentSpec: Spec; + + matchersClass: Matchers; + + version(): any; + versionString(): string; + nextSpecId(): number; + addReporter(reporter: Reporter): void; + execute(): void; + describe(description: string, specDefinitions: () => void): Suite; + // ddescribe(description: string, specDefinitions: () => void): Suite; Not a part of jasmine. Angular team adds these + beforeEach(beforeEachFunction: () => void): void; + currentRunner(): Runner; + afterEach(afterEachFunction: () => void): void; + xdescribe(desc: string, specDefinitions: () => void): XSuite; + it(description: string, func: () => void): Spec; + // iit(description: string, func: () => void): Spec; Not a part of jasmine. Angular team adds these + xit(desc: string, func: () => void): XSpec; + compareRegExps_(a: RegExp, b: RegExp, mismatchKeys: string[], mismatchValues: string[]): boolean; + compareObjects_(a: any, b: any, mismatchKeys: string[], mismatchValues: string[]): boolean; + equals_(a: any, b: any, mismatchKeys: string[], mismatchValues: string[]): boolean; + contains_(haystack: any, needle: any): boolean; + addEqualityTester(equalityTester: (a: any, b: any, env: Env, mismatchKeys: string[], mismatchValues: string[]) => boolean): void; + specFilter(spec: Spec): boolean; + } + + interface FakeTimer { + + new (): any; + + reset(): void; + tick(millis: number): void; + runFunctionsWithinRange(oldMillis: number, nowMillis: number): void; + scheduleFunction(timeoutKey: any, funcToCall: () => void, millis: number, recurring: boolean): void; + } + + interface HtmlReporter { + new (): any; + } + + interface HtmlSpecFilter { + new (): any; + } + + interface Result { + type: string; + } + + interface NestedResults extends Result { + description: string; + + totalCount: number; + passedCount: number; + failedCount: number; + + skipped: boolean; + + rollupCounts(result: NestedResults): void; + log(values: any): void; + getItems(): Result[]; + addResult(result: Result): void; + passed(): boolean; + } + + interface MessageResult extends Result { + values: any; + trace: Trace; + } + + interface ExpectationResult extends Result { + matcherName: string; + passed(): boolean; + expected: any; + actual: any; + message: string; + trace: Trace; + } + + interface Trace { + name: string; + message: string; + stack: any; + } + + interface PrettyPrinter { + + new (): any; + + format(value: any): void; + iterateObject(obj: any, fn: (property: string, isGetter: boolean) => void): void; + emitScalar(value: any): void; + emitString(value: string): void; + emitArray(array: any[]): void; + emitObject(obj: any): void; + append(value: any): void; + } + + interface StringPrettyPrinter extends PrettyPrinter { + } + + interface Queue { + + new (env: any): any; + + env: Env; + ensured: boolean[]; + blocks: Block[]; + running: boolean; + index: number; + offset: number; + abort: boolean; + + addBefore(block: Block, ensure?: boolean): void; + add(block: any, ensure?: boolean): void; + insertNext(block: any, ensure?: boolean): void; + start(onComplete?: () => void): void; + isRunning(): boolean; + next_(): void; + results(): NestedResults; + } + + interface Matchers { + + new (env: Env, actual: any, spec: Env, isNot?: boolean): any; + + env: Env; + actual: any; + spec: Env; + isNot?: boolean; + message(): any; + + toBe(expected: any): boolean; + toEqual(expected: any): boolean; + toMatch(expected: any): boolean; + toBeDefined(): boolean; + toBeUndefined(): boolean; + toBeNull(): boolean; + toBeNaN(): boolean; + toBeTruthy(): boolean; + toBeFalsy(): boolean; + toHaveBeenCalled(): boolean; + toHaveBeenCalledWith(...params: any[]): boolean; + toContain(expected: any): boolean; + toBeLessThan(expected: any): boolean; + toBeGreaterThan(expected: any): boolean; + toBeCloseTo(expected: any, precision: any): boolean; + toContainHtml(expected: string): boolean; + toContainText(expected: string): boolean; + toThrow(expected?: any): boolean; + toThrowError(expected?: any): boolean; + not: Matchers; + + Any: Any; + } + + interface Reporter { + reportRunnerStarting(runner: Runner): void; + reportRunnerResults(runner: Runner): void; + reportSuiteResults(suite: Suite): void; + reportSpecStarting(spec: Spec): void; + reportSpecResults(spec: Spec): void; + log(str: string): void; + } + + interface MultiReporter extends Reporter { + addReporter(reporter: Reporter): void; + } + + interface Runner { + + new (env: Env): any; + + execute(): void; + beforeEach(beforeEachFunction: SpecFunction): void; + afterEach(afterEachFunction: SpecFunction): void; + finishCallback(): void; + addSuite(suite: Suite): void; + add(block: Block): void; + specs(): Spec[]; + suites(): Suite[]; + topLevelSuites(): Suite[]; + results(): NestedResults; + } + + interface SpecFunction { + (spec?: Spec): void; + } + + interface SuiteOrSpec { + id: number; + env: Env; + description: string; + queue: Queue; + } + + interface Spec extends SuiteOrSpec { + + new (env: Env, suite: Suite, description: string): any; + + suite: Suite; + + afterCallbacks: SpecFunction[]; + spies_: Spy[]; + + results_: NestedResults; + matchersClass: Matchers; + + getFullName(): string; + results(): NestedResults; + log(arguments: any): any; + runs(func: SpecFunction): Spec; + addToQueue(block: Block): void; + addMatcherResult(result: Result): void; + expect(actual: any): any; + waits(timeout: number): Spec; + waitsFor(latchFunction: SpecFunction, timeoutMessage?: string, timeout?: number): Spec; + fail(e?: any): void; + getMatchersClass_(): Matchers; + addMatchers(matchersPrototype: any): void; + finishCallback(): void; + finish(onComplete?: () => void): void; + after(doAfter: SpecFunction): void; + execute(onComplete?: () => void): any; + addBeforesAndAftersToQueue(): void; + explodes(): void; + spyOn(obj: any, methodName: string, ignoreMethodDoesntExist: boolean): Spy; + removeAllSpies(): void; + } + + interface XSpec { + id: number; + runs(): void; + } + + interface Suite extends SuiteOrSpec { + + new (env: Env, description: string, specDefinitions: () => void, parentSuite: Suite): any; + + parentSuite: Suite; + + getFullName(): string; + finish(onComplete?: () => void): void; + beforeEach(beforeEachFunction: SpecFunction): void; + afterEach(afterEachFunction: SpecFunction): void; + results(): NestedResults; + add(suiteOrSpec: SuiteOrSpec): void; + specs(): Spec[]; + suites(): Suite[]; + children(): any[]; + execute(onComplete?: () => void): void; + } + + interface XSuite { + execute(): void; + } + + interface Spy { + (...params: any[]): any; + + identity: string; + and: SpyAnd; + calls: Calls; + mostRecentCall: { args: any[]; }; + argsForCall: any[]; + wasCalled: boolean; + callCount: number; + } + + interface SpyAnd { + /** By chaining the spy with and.callThrough, the spy will still track all calls to it but in addition it will delegate to the actual implementation. */ + callThrough(): void; + /** By chaining the spy with and.returnValue, all calls to the function will return a specific value. */ + returnValue(val: any): void; + /** By chaining the spy with and.callFake, all calls to the spy will delegate to the supplied function. */ + callFake(fn: Function): void; + /** By chaining the spy with and.throwError, all calls to the spy will throw the specified value. */ + throwError(msg: string): void; + /** When a calling strategy is used for a spy, the original stubbing behavior can be returned at any time with and.stub. */ + stub(): void; + } + + interface Calls { + /** By chaining the spy with calls.any(), will return false if the spy has not been called at all, and then true once at least one call happens. **/ + any(): boolean; + /** By chaining the spy with calls.count(), will return the number of times the spy was called **/ + count(): number; + /** By chaining the spy with calls.argsFor(), will return the arguments passed to call number index **/ + argsFor(index: number): any[]; + /** By chaining the spy with calls.allArgs(), will return the arguments to all calls **/ + allArgs(): any[]; + /** By chaining the spy with calls.all(), will return the context (the this) and arguments passed all calls **/ + all(): any; + /** By chaining the spy with calls.mostRecent(), will return the context (the this) and arguments for the most recent call **/ + mostRecent(): any; + /** By chaining the spy with calls.first(), will return the context (the this) and arguments for the first call **/ + first(): any; + /** By chaining the spy with calls.reset(), will clears all tracking for a spy **/ + reset(): void; + } + + interface Util { + inherit(childClass: Function, parentClass: Function): any; + formatException(e: any): any; + htmlEscape(str: string): string; + argsToArray(args: any): any; + extend(destination: any, source: any): any; + } + + interface JsApiReporter extends Reporter { + + started: boolean; + finished: boolean; + result: any; + messages: any; + + new (): any; + + suites(): Suite[]; + summarize_(suiteOrSpec: SuiteOrSpec): any; + results(): any; + resultsForSpec(specId: any): any; + log(str: any): any; + resultsForSpecs(specIds: any): any; + summarizeResult_(result: any): any; + } + + interface Jasmine { + Spec: Spec; + clock: Clock; + util: Util; + } + + export var HtmlReporter: HtmlReporter; + export var HtmlSpecFilter: HtmlSpecFilter; + export var DEFAULT_TIMEOUT_INTERVAL: number; +} diff --git a/src/scripts/game/Battle.ts b/src/scripts/game/Battle.ts index 42d9def..643a551 100644 --- a/src/scripts/game/Battle.ts +++ b/src/scripts/game/Battle.ts @@ -10,7 +10,26 @@ module SpaceTac.Game { // Create a battle between two fleets constructor(fleet1: Fleet, fleet2: Fleet) { this.fleets = [fleet1, fleet2]; - // TODO Initiative throws to set the play order + this.play_order = []; + } + + // Create play order, performing an initiative throw + throwInitiative(gen: RandomGenerator) { + var play_order: Ship[] = []; + + // Throw each ship's initiative + this.fleets.forEach(function(fleet){ + fleet.ships.forEach(function(ship){ + ship.throwInitiative(gen); + play_order.push(ship); + }); + }); + + // Sort by throw result + play_order.sort(function(ship1: Ship, ship2: Ship) { + return (ship2.initative_throw - ship1.initative_throw); + }); + this.play_order = play_order; } // Create a quick random battle, for testing purposes diff --git a/src/scripts/game/Fleet.ts b/src/scripts/game/Fleet.ts index 6189e1c..ba281e6 100644 --- a/src/scripts/game/Fleet.ts +++ b/src/scripts/game/Fleet.ts @@ -10,6 +10,13 @@ module SpaceTac.Game { // Create a fleet, bound to a player constructor(player: Player) { this.player = player; + this.ships = []; + } + + // Add a ship in this fleet + addShip(ship: Ship): void { + ship.fleet = this; + this.ships.push(ship); } } } \ No newline at end of file diff --git a/src/scripts/game/RandomGenerator.ts b/src/scripts/game/RandomGenerator.ts new file mode 100644 index 0000000..9a1bca9 --- /dev/null +++ b/src/scripts/game/RandomGenerator.ts @@ -0,0 +1,29 @@ +module SpaceTac.Game { + // Random generator, used in all throws + export class RandomGenerator { + // Array of next values, empty for a correct generator + private fake_values: number[]; + + // Basic constructor (can specify fake values as arguments) + constructor(...values: number[]) { + this.fake_values = values; + } + + // Generate a value, based on an attribute level + throw(level: number): number { + if (this.fake_values.length > 0) { + return this.fake_values.shift() * level; + } + else { + return Math.random() * level; + } + } + + // Fake the generator, by forcing the next value + // Call it several times to set future successive values + // This value will replace the 0.0-1.0 random value, not the final one + forceNextValue(value: number): void { + this.fake_values.push(value); + } + } +} \ No newline at end of file diff --git a/src/scripts/game/Ship.ts b/src/scripts/game/Ship.ts index 2b4e403..840bd0d 100644 --- a/src/scripts/game/Ship.ts +++ b/src/scripts/game/Ship.ts @@ -4,6 +4,9 @@ module SpaceTac.Game { // Fleet this ship is a member of fleet: Fleet; + // Name of the ship + name: string; + // Current level level: number; @@ -13,7 +16,29 @@ module SpaceTac.Game { // Number of hull points hull: number; - // Current initiative (high numbers will allow this ship to play sooner) - initiative: number; + // Current initiative level (high numbers will allow this ship to play sooner) + initiative_level: number; + + // Last initiative throw + initative_throw: number; + + // Create a new ship inside a fleet + constructor(fleet: Fleet, name: string) { + this.fleet = fleet; + this.name = name; + this.initiative_level = 1; + + fleet.addShip(this); + } + + // String repr + jasmineToString(): string { + return "Ship " + this.name; + } + + // Make an initiative throw, to resolve play order in a battle + throwInitiative(gen: RandomGenerator): void { + this.initative_throw = gen.throw(this.initiative_level); + } } } \ No newline at end of file diff --git a/src/scripts/specs/Battle.spec.ts b/src/scripts/specs/Battle.spec.ts new file mode 100644 index 0000000..fdae0dd --- /dev/null +++ b/src/scripts/specs/Battle.spec.ts @@ -0,0 +1,30 @@ +/// + +module SpaceTac.Specs { + describe("Battle", function(){ + it("defines play order by initiative throws", function(){ + var fleet1 = new Game.Fleet(null); + var fleet2 = new Game.Fleet(null); + + var ship1 = new Game.Ship(fleet1, "F1S1"); + ship1.initiative_level = 2; + var ship2 = new Game.Ship(fleet1, "F1S2"); + ship2.initiative_level = 4; + var ship3 = new Game.Ship(fleet1, "F1S3"); + ship3.initiative_level = 1; + var ship4 = new Game.Ship(fleet2, "F2S1"); + ship4.initiative_level = 8; + var ship5 = new Game.Ship(fleet2, "F2S2"); + ship5.initiative_level = 2; + + var battle = new Game.Battle(fleet1, fleet2); + expect(battle.play_order.length).toBe(0); + + var gen = new Game.RandomGenerator(1.0, 0.1, 1.0, 0.2, 0.6); + battle.throwInitiative(gen); + + expect(battle.play_order.length).toBe(5); + expect(battle.play_order).toEqual([ship1, ship4, ship5, ship3, ship2]); + }); + }); +}