diff --git a/src/core/Battle.ts b/src/core/Battle.ts index cf832f1..6cf0977 100644 --- a/src/core/Battle.ts +++ b/src/core/Battle.ts @@ -36,6 +36,9 @@ module TS.SpaceTac { // Timer to use for scheduled things timer = Timer.global + // Indicator that an AI is playing + ai_playing = false + // Create a battle between two fleets constructor(fleet1 = new Fleet(), fleet2 = new Fleet(), width = 1808, height = 948) { this.fleets = [fleet1, fleet2]; @@ -243,6 +246,8 @@ module TS.SpaceTac { this.playing_ship.startTurn(); } + this.ai_playing = false; + if (log && previous_ship && this.playing_ship) { this.log.add(new ShipChangeEvent(previous_ship, this.playing_ship)); } @@ -251,13 +256,17 @@ module TS.SpaceTac { /** * Make an AI play the current ship */ - playAI(ai: AbstractAI | null = null) { - if (this.playing_ship) { + playAI(ai: AbstractAI | null = null): boolean { + if (this.playing_ship && !this.ai_playing) { + this.ai_playing = true; if (!ai) { // TODO Use an AI adapted to the fleet ai = new TacticalAI(this.playing_ship, this.timer); } ai.play(); + return true; + } else { + return false; } } diff --git a/src/core/events/BaseBattleEvent.ts b/src/core/events/BaseBattleEvent.ts index f77231a..aa12e78 100644 --- a/src/core/events/BaseBattleEvent.ts +++ b/src/core/events/BaseBattleEvent.ts @@ -25,15 +25,28 @@ module TS.SpaceTac { /** * Apply the event on a battle state + * + * By default it does nothing */ apply(battle: Battle) { } /** * Get the reverse event + * + * By default it returns a stub event that does nothing */ getReverse(): BaseBattleEvent { - throw new Error("No reverse implemented"); + return new StubBattleEvent(); + } + } + + /** + * Battle event that does nothing + */ + export class StubBattleEvent extends BaseBattleEvent { + constructor() { + super("stub"); } } diff --git a/src/core/events/DroneDeployedEvent.ts b/src/core/events/DroneDeployedEvent.ts index fd220ad..de86aa7 100644 --- a/src/core/events/DroneDeployedEvent.ts +++ b/src/core/events/DroneDeployedEvent.ts @@ -11,5 +11,9 @@ module TS.SpaceTac { this.drone = drone; } + + getReverse(): BaseBattleEvent { + return new DroneDestroyedEvent(this.drone); + } } } diff --git a/src/core/events/DroneDestroyedEvent.ts b/src/core/events/DroneDestroyedEvent.ts index 0755874..5f23157 100644 --- a/src/core/events/DroneDestroyedEvent.ts +++ b/src/core/events/DroneDestroyedEvent.ts @@ -11,5 +11,9 @@ module TS.SpaceTac { this.drone = drone; } + + getReverse(): BaseBattleEvent { + return new DroneDeployedEvent(this.drone); + } } } diff --git a/src/core/events/ShipChangeEvent.spec.ts b/src/core/events/ShipChangeEvent.spec.ts new file mode 100644 index 0000000..d024655 --- /dev/null +++ b/src/core/events/ShipChangeEvent.spec.ts @@ -0,0 +1,10 @@ +module TS.SpaceTac.Specs { + describe("ShipChangeEvent", function () { + it("get reverse event", function () { + let ship1 = new Ship(); + let ship2 = new Ship(); + let event = new ShipChangeEvent(ship1, ship2); + expect(event.getReverse()).toEqual(new ShipChangeEvent(ship2, ship1)); + }); + }); +} \ No newline at end of file diff --git a/src/core/events/ShipChangeEvent.ts b/src/core/events/ShipChangeEvent.ts index f63d5a4..5371a22 100644 --- a/src/core/events/ShipChangeEvent.ts +++ b/src/core/events/ShipChangeEvent.ts @@ -1,7 +1,9 @@ /// module TS.SpaceTac { - // Battle event, when a ship turn ended, and advanced to a new one + /** + * Event that changes the current playing ship + */ export class ShipChangeEvent extends BaseLogShipEvent { // Ship that starts playing new_ship: Ship; @@ -11,5 +13,9 @@ module TS.SpaceTac { this.new_ship = new_ship; } + + getReverse(): BaseBattleEvent { + return new ShipChangeEvent(this.new_ship, this.ship); + } } } diff --git a/src/core/events/ValueChangeEvent.spec.ts b/src/core/events/ValueChangeEvent.spec.ts new file mode 100644 index 0000000..b72ca20 --- /dev/null +++ b/src/core/events/ValueChangeEvent.spec.ts @@ -0,0 +1,9 @@ +module TS.SpaceTac.Specs { + describe("ValueChangeEvent", function () { + it("get reverse event", function () { + let ship = new Ship(); + let event = new ValueChangeEvent(ship, new ShipValue("hull", 15, 22), 10); + expect(event.getReverse()).toEqual(new ValueChangeEvent(ship, new ShipValue("hull", 5, 22), -10)); + }); + }); +} \ No newline at end of file diff --git a/src/core/events/ValueChangeEvent.ts b/src/core/events/ValueChangeEvent.ts index 12bbd9d..38dacb5 100644 --- a/src/core/events/ValueChangeEvent.ts +++ b/src/core/events/ValueChangeEvent.ts @@ -1,7 +1,9 @@ /// module TS.SpaceTac { - // Event logged when a ship value or attribute changed + /** + * Event logged when a ship value or attribute changed + */ export class ValueChangeEvent extends BaseLogShipEvent { // Saved version of the current value value: ShipValue; @@ -15,5 +17,11 @@ module TS.SpaceTac { this.value = copy(value); this.diff = diff; } + + getReverse(): BaseBattleEvent { + let value = copy(this.value); + value.set(value.get() - this.diff); + return new ValueChangeEvent(this.ship, value, -this.diff); + } } } diff --git a/src/ui/battle/BattleView.ts b/src/ui/battle/BattleView.ts index 10ead13..c93861c 100644 --- a/src/ui/battle/BattleView.ts +++ b/src/ui/battle/BattleView.ts @@ -128,18 +128,26 @@ module TS.SpaceTac.UI { }); this.battle.endBattle(first(this.battle.fleets, fleet => fleet.player != this.player)); }); - this.inputs.bindCheat("a", "Use AI to play", () => { - if (this.interacting && this.battle.playing_ship) { - this.setInteractionEnabled(false); - this.action_bar.setShip(new Ship()); - this.battle.playAI(new TacticalAI(this.battle.playing_ship)); - } - }); + this.inputs.bindCheat("a", "Use AI to play", () => this.playAI()); // Start processing the log this.log_processor.start(); } + /** + * Make the AI play current ship + * + * If the AI is already playing, do nothing + */ + playAI(): void { + if (this.battle.playAI()) { + if (this.interacting) { + this.action_bar.setShip(new Ship()); + } + this.setInteractionEnabled(false); + } + } + // Leaving the view, we unbind the battle shutdown() { this.exitTargettingMode(); diff --git a/src/ui/battle/LogProcessor.spec.ts b/src/ui/battle/LogProcessor.spec.ts index 88319a3..27b7a1e 100644 --- a/src/ui/battle/LogProcessor.spec.ts +++ b/src/ui/battle/LogProcessor.spec.ts @@ -32,27 +32,43 @@ module TS.SpaceTac.UI.Specs { return 0; }); expect(battle.turn).toBe(1); + expect(processor.atStart()).toBe(true); + expect(processor.atEnd()).toBe(true); processor.stepForward(); expect(battle.turn).toBe(1); + expect(processor.atStart()).toBe(true); + expect(processor.atEnd()).toBe(true); battle.log.add(new FakeEvent()); expect(battle.turn).toBe(1); + expect(processor.atStart()).toBe(true); + expect(processor.atEnd()).toBe(false); processor.stepForward(); expect(battle.turn).toBe(2); + expect(processor.atStart()).toBe(false); + expect(processor.atEnd()).toBe(true); processor.stepForward(); expect(battle.turn).toBe(2); + expect(processor.atStart()).toBe(false); + expect(processor.atEnd()).toBe(true); processor.stepBackward(); expect(battle.turn).toBe(1); + expect(processor.atStart()).toBe(true); + expect(processor.atEnd()).toBe(false); processor.stepBackward(); expect(battle.turn).toBe(1); + expect(processor.atStart()).toBe(true); + expect(processor.atEnd()).toBe(false); processor.stepForward(); expect(battle.turn).toBe(2); + expect(processor.atStart()).toBe(false); + expect(processor.atEnd()).toBe(true); }) }) } \ No newline at end of file diff --git a/src/ui/battle/LogProcessor.ts b/src/ui/battle/LogProcessor.ts index 144551a..28e8dda 100644 --- a/src/ui/battle/LogProcessor.ts +++ b/src/ui/battle/LogProcessor.ts @@ -56,6 +56,7 @@ module TS.SpaceTac.UI { */ start() { this.subscription = this.log.subscribe(event => this.processBattleEvent(event)); + this.cursor = this.log.events.length - 1; this.battle.getBootstrapEvents().forEach(event => this.processBattleEvent(event)); } @@ -63,9 +64,9 @@ module TS.SpaceTac.UI { * Make a step backward in time */ stepBackward() { - if (this.cursor >= 0) { - this.processBattleEvent(this.log.events[this.cursor].getReverse()); + if (!this.atStart()) { this.cursor -= 1; + this.processBattleEvent(this.log.events[this.cursor + 1].getReverse()); } } @@ -73,7 +74,7 @@ module TS.SpaceTac.UI { * Make a step forward in time */ stepForward() { - if (this.cursor < this.log.events.length - 1) { + if (!this.atEnd()) { this.cursor += 1; this.processBattleEvent(this.log.events[this.cursor]); } @@ -85,6 +86,9 @@ module TS.SpaceTac.UI { * This will rewind all applied event */ jumpToStart() { + while (!this.atStart()) { + this.stepBackward(); + } } /** @@ -93,6 +97,34 @@ module TS.SpaceTac.UI { * This will apply all remaining event */ jumpToEnd() { + while (!this.atEnd()) { + this.stepForward(); + } + } + + /** + * Check if we are currently at the start of the log + */ + atStart(): boolean { + return this.cursor < 0; + } + + /** + * Check if we are currently at the end of the log + */ + atEnd(): boolean { + return this.cursor >= this.log.events.length - 1; + } + + /** + * Check if we need a player or AI to interact at this point + */ + getPlayerNeeded(): Player | null { + if (this.atEnd()) { + return this.battle.playing_ship ? this.battle.playing_ship.getPlayer() : null; + } else { + return null; + } } /** @@ -176,6 +208,28 @@ module TS.SpaceTac.UI { } else if (event instanceof DroneAppliedEvent) { this.processDroneAppliedEvent(event); } + + // FIXME temporary fix for cursor not being forwarded + let cursor = this.log.events.indexOf(event); + if (cursor >= 0) { + this.cursor = cursor; + } + + // Transfer control to the needed player + let player = this.getPlayerNeeded(); + if (player) { + if (this.battle.playing_ship && !this.battle.playing_ship.alive) { + this.view.setInteractionEnabled(false); + this.battle.advanceToNextShip(); + this.delayNextEvents(200); + } else if (player === this.view.player) { + this.view.setInteractionEnabled(true); + } else { + this.view.playAI(); + } + } else { + this.view.setInteractionEnabled(false); + } } // Destroy the log processor @@ -191,24 +245,7 @@ module TS.SpaceTac.UI { private processShipChangeEvent(event: ShipChangeEvent): void { this.view.arena.setShipPlaying(event.new_ship); this.view.ship_list.setPlaying(event.new_ship); - - if (this.battle.canPlay(this.view.player)) { - // Player turn - this.view.gameui.audio.playOnce("battle-ship-change"); - this.view.setInteractionEnabled(true); - } else { - this.view.setInteractionEnabled(false); - if (event.new_ship.isAbleToPlay()) { - // AI turn - this.view.gameui.audio.playOnce("battle-ship-change"); - this.battle.playAI(); - } else { - // Ship unable to play, skip turn - this.view.timer.schedule(event.new_ship.alive ? 2000 : 200, () => { - this.battle.advanceToNextShip(); - }); - } - } + this.view.gameui.audio.playOnce("battle-ship-change"); } // Damage to ship