From 9aa92a58fa4c507a737afeadcd09ab6ea36b4e2f Mon Sep 17 00:00:00 2001 From: Paul Grebenc Date: Sat, 11 Jan 2025 21:18:09 -0500 Subject: [PATCH 01/32] New wave with introduction of enemies. --- src/Game.ts | 4 +- src/enemies/Zagger.ts | 174 ++++++++++++++++++++++++++++++++++++++++ src/waves/ZigZagWave.ts | 57 +++++++++++++ 3 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 src/enemies/Zagger.ts create mode 100644 src/waves/ZigZagWave.ts diff --git a/src/Game.ts b/src/Game.ts index fd076b2..16df3aa 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -22,6 +22,7 @@ import {SplitterWave} from './waves/SplitterWave'; import {StarField} from './StarField'; import {TextInterlude} from './TextInterlude'; import {World} from './World'; +import { ZigZagWave } from "./waves/ZigZagWave"; export class Game { private readonly _joystick: Joystick; @@ -92,7 +93,7 @@ export class Game { if (this._isActive) { setTimeout(() => { global.requestAnimationFrame(() => { this.tick() }); - }, 1000/30); + }, 1000/40); } else { debug('ticker: Ticker stopping because game is over.'); @@ -177,6 +178,7 @@ export class Game { this._clock, [ new Level([ + new ZigZagWave(this._audioPlayer, this._world, this._clock), new SpinnerWave(this._audioPlayer, this._world, this._clock), new SimpleWave(this._audioPlayer, this._world, this._clock) ]), diff --git a/src/enemies/Zagger.ts b/src/enemies/Zagger.ts new file mode 100644 index 0000000..0ba0c38 --- /dev/null +++ b/src/enemies/Zagger.ts @@ -0,0 +1,174 @@ +import Debug from "debug"; +const debug = Debug("Blaster:Saucer"); +import {random} from 'underscore'; + +import {Actor} from "../Actor"; +import {AudioPlayer} from "../devices/AudioPlayer"; +import {Bomb} from '../shots/Bomb'; +import {Bounds} from '../Bounds'; +import {Clock} from "../timing/Clock"; +import {Enemy} from './Enemy'; +import {ExplosionProperties} from '../ExplosionProperties'; +import {HitArbiter} from '../HitArbiter'; +import {ImageDetails} from '../ImageDetails'; +import {LinePath} from '../paths/LinePath'; +import {PathAction} from '../paths/PathAction'; +import {PathEntry} from "../paths/PathEntry"; +import {Point} from '../Point'; +import {ScheduledAction} from '../paths/ScheduledAction'; +import {Scheduler} from '../timing/Scheduler'; +import {SplinePath} from '../paths/SplinePath'; +import {World} from "../World"; + +export class Zagger extends Enemy { + public static readonly InitialHealth: number = 1; + + private readonly _scheduler: Scheduler; + private readonly _hitArbiter: HitArbiter; + private _currentFrame: number = 0; + private _currentPath!: PathEntry[]; + private _currentPathTemplate!: PathEntry[]; + private _pathPosition!: number; + + constructor(audioPlayer: AudioPlayer, world: World, clock: Clock, startingPoint: Point, homePosition: Point) { + super(audioPlayer, world, startingPoint, Zagger.InitialHealth); + debug('Zagger constructor'); + + this._scheduler = new Scheduler(clock); + this._hitArbiter = new HitArbiter(this); + + this.prepareEntryPath(homePosition); + this.advanceCurrentFrame(); + } + + get explosionProperties(): ExplosionProperties { + return new ExplosionProperties( + 'saucer_explosion', + 4, + 80, + 0.8, + 'saucer_explosion' + ); + } + + get scoreTotal(): number { + return 10; + } + + getCollisionMask(actor: Actor): Bounds[] { + return [new Bounds(-20, 20, -20, 20)]; + } + + getDamageAgainst(target: Actor): number { + return 5; + } + + get imageDetails(): ImageDetails { + return new ImageDetails('saucer', 4, 80, this._currentFrame); + } + + hitBy(actor: Actor, damage: number): boolean { + this._health = Math.max(0, this._health - damage); + return true; + } + + tick(): void { + debug('Zagger.tick'); + super.tick(); + + if (!this._isActive) { + return; + } + + this._scheduler.executeDueOperations(); + + this.step(); + + // Check if this saucer has collided with any active enemies. + const player = this._world.player; + if (player) { + this._hitArbiter.attemptToHit(player); + } + } + + private advanceCurrentFrame(): void { + this._currentFrame = (this._currentFrame + 1) % 4; + + this._scheduler.scheduleOperation( + 'advanceCurrentFrame', + 200, + () => { this.advanceCurrentFrame() } + ); + } + + private dropBomb(): void { + const bomb = new Bomb(this._audioPlayer, this._world, this._location); + this._world.addActor(bomb); + } + + private step(): void { + // Choose the next path to follow once we've reach the end of the current path. + if (this._pathPosition >= this._currentPath.length) { + return; + //let nextPath: PathEntry[]; + + // What do we do once we reach home... + // if (this._currentPathTemplate === Zagger._floatAroundPathTemplate) { + // if (random(0, 1) > 0.5) { + // if (this._location.x < this._world.dimensions.width / 2) { + // nextPath = Zagger._flyRightPathTemplate; + // } + // else { + // nextPath = Zagger._flyLeftPathTemplate; + // } + // } + // else { + // if (this._location.y < this._world.dimensions.height / 2) { + // if (random(0, 1) > 0.5) { + // nextPath = Zagger._flyDownPathTemplate; + // } + // else { + // if (this._location.x < this._world.dimensions.width / 2) { + // nextPath = Zagger._diveRightPathTemplate; + // } + // else { + // nextPath = Zagger._diveLeftPathTemplate; + // } + // } + // } + // else { + // nextPath = Zagger._flyUpPathTemplate; + // } + // } + // } + // else { + // nextPath = Zagger._floatAroundPathTemplate; + // } + + // this.prepareNextPath(nextPath); + } + + // Follow the current path. + switch(this._currentPath[this._pathPosition].action) { + case PathAction.Move: + this._location = this._currentPath[this._pathPosition].location!; + break; + case PathAction.Fire: + this.dropBomb(); + break; + } + this._pathPosition++; + } + + private prepareEntryPath(homePosition: Point): void { + const linePath = new LinePath(this._location, homePosition, []); + this._currentPath = linePath.getPath(20); + this._pathPosition = 0; + } + + private prepareNextPath(pathTemplate: PathEntry[]): void { + this._currentPathTemplate = pathTemplate; + this._currentPath = SplinePath.translatePath(pathTemplate, this._location.x, this._location.y); + this._pathPosition = 0; + } +} diff --git a/src/waves/ZigZagWave.ts b/src/waves/ZigZagWave.ts new file mode 100644 index 0000000..9c89681 --- /dev/null +++ b/src/waves/ZigZagWave.ts @@ -0,0 +1,57 @@ +import Debug from "debug"; +const debug = Debug("Blaster:SimpleWave"); +import {random} from 'underscore'; + +import {AudioPlayer} from "../devices/AudioPlayer"; +import {Clock} from "../timing/Clock"; +import {Point} from '../Point'; +import {Zagger} from '../enemies/Zagger'; +import {Wave} from './Wave'; +import {World} from "../World"; + +export class ZigZagWave implements Wave { + private readonly _audioPlayer: AudioPlayer; + private readonly _world: World; + private readonly _clock: Clock; + private _addNextEnemyAt: Date = new Date(); + private _numberOfEnemiesLeftToDeploy: number = 15; + + constructor(audioPlayer: AudioPlayer, world: World, clock: Clock) { + debug('ZigZagWave constructor'); + this._audioPlayer = audioPlayer; + this._world = world; + this._clock = clock; + } + + get isActive(): boolean { + return (this._numberOfEnemiesLeftToDeploy > 0) + || (this._world.activeEnemies.length > 0) + || (this._world.activeExplosions.length > 0); + } + + tick(): void { + debug('ZigZagWave.tick'); + + //TODO: This is just a simple demonstration of how a wave can manage adding enemies to the world. + if (this._numberOfEnemiesLeftToDeploy > 0) { + // Add new enemy when the time comes, but only if a maximum allowed aren't already active. + if ((this._addNextEnemyAt <= new Date()) && (this._world.activeEnemies.length < 15)) { + // Space out the addition of enemies. + this._addNextEnemyAt = new Date(Date.now() + 250); + + const worldDimensions = this._world.dimensions; + const zaggerStartingPoint = new Point( + Math.floor(random(0, worldDimensions.width-50)), + -20 + ); + const zaggerHomeX = (5-(this._numberOfEnemiesLeftToDeploy-1)%5) * 80; + const zaggerHomeY = 100 + Math.floor((this._numberOfEnemiesLeftToDeploy-1)/5) * 70; + const zaggerHomePosition = new Point(zaggerHomeX, zaggerHomeY); + const zagger = new Zagger(this._audioPlayer, this._world, this._clock, zaggerStartingPoint, zaggerHomePosition); + this._world.addActor(zagger); + + this._numberOfEnemiesLeftToDeploy--; + } + } + } +} From ad86087d9022638f09806e7e8ddd1e2ae2c66cb0 Mon Sep 17 00:00:00 2001 From: Paul Grebenc Date: Sat, 11 Jan 2025 21:40:27 -0500 Subject: [PATCH 02/32] Use scheduler in deploying zaggers. --- src/waves/ZigZagWave.ts | 54 ++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/waves/ZigZagWave.ts b/src/waves/ZigZagWave.ts index 9c89681..2c49260 100644 --- a/src/waves/ZigZagWave.ts +++ b/src/waves/ZigZagWave.ts @@ -5,9 +5,10 @@ import {random} from 'underscore'; import {AudioPlayer} from "../devices/AudioPlayer"; import {Clock} from "../timing/Clock"; import {Point} from '../Point'; -import {Zagger} from '../enemies/Zagger'; +import {Scheduler} from '../timing/Scheduler'; import {Wave} from './Wave'; import {World} from "../World"; +import {Zagger} from '../enemies/Zagger'; export class ZigZagWave implements Wave { private readonly _audioPlayer: AudioPlayer; @@ -16,11 +17,21 @@ export class ZigZagWave implements Wave { private _addNextEnemyAt: Date = new Date(); private _numberOfEnemiesLeftToDeploy: number = 15; + private readonly _scheduler: Scheduler; + constructor(audioPlayer: AudioPlayer, world: World, clock: Clock) { debug('ZigZagWave constructor'); this._audioPlayer = audioPlayer; this._world = world; this._clock = clock; + + this._scheduler = new Scheduler(clock); + + this._scheduler.scheduleOperation( + 'deploy', + 0, + () => { this.deployZagger() } + ); } get isActive(): boolean { @@ -32,26 +43,29 @@ export class ZigZagWave implements Wave { tick(): void { debug('ZigZagWave.tick'); - //TODO: This is just a simple demonstration of how a wave can manage adding enemies to the world. + this._scheduler.executeDueOperations(); + } + + deployZagger(): void { if (this._numberOfEnemiesLeftToDeploy > 0) { - // Add new enemy when the time comes, but only if a maximum allowed aren't already active. - if ((this._addNextEnemyAt <= new Date()) && (this._world.activeEnemies.length < 15)) { - // Space out the addition of enemies. - this._addNextEnemyAt = new Date(Date.now() + 250); - - const worldDimensions = this._world.dimensions; - const zaggerStartingPoint = new Point( - Math.floor(random(0, worldDimensions.width-50)), - -20 - ); - const zaggerHomeX = (5-(this._numberOfEnemiesLeftToDeploy-1)%5) * 80; - const zaggerHomeY = 100 + Math.floor((this._numberOfEnemiesLeftToDeploy-1)/5) * 70; - const zaggerHomePosition = new Point(zaggerHomeX, zaggerHomeY); - const zagger = new Zagger(this._audioPlayer, this._world, this._clock, zaggerStartingPoint, zaggerHomePosition); - this._world.addActor(zagger); - - this._numberOfEnemiesLeftToDeploy--; - } + const worldDimensions = this._world.dimensions; + const zaggerStartingPoint = new Point( + Math.floor(random(0, worldDimensions.width-50)), + -20 + ); + const zaggerHomeX = (5-(this._numberOfEnemiesLeftToDeploy-1)%5) * 80; + const zaggerHomeY = 100 + Math.floor((this._numberOfEnemiesLeftToDeploy-1)/5) * 70; + const zaggerHomePosition = new Point(zaggerHomeX, zaggerHomeY); + const zagger = new Zagger(this._audioPlayer, this._world, this._clock, zaggerStartingPoint, zaggerHomePosition); + this._world.addActor(zagger); + + this._numberOfEnemiesLeftToDeploy--; + + this._scheduler.scheduleOperation( + 'deploy', + 250, + () => { this.deployZagger() } + ); } } } From 4b4184f617aa1df9b6fd86c8252bbfbde136ad4b Mon Sep 17 00:00:00 2001 From: Paul Grebenc Date: Tue, 14 Jan 2025 21:46:26 -0500 Subject: [PATCH 03/32] Format code. --- test/timing/Scheduler.spec.ts | 106 +++++++++++++++++----------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/test/timing/Scheduler.spec.ts b/test/timing/Scheduler.spec.ts index c1efc5a..1d56436 100644 --- a/test/timing/Scheduler.spec.ts +++ b/test/timing/Scheduler.spec.ts @@ -1,27 +1,27 @@ -import {describe} from 'mocha'; -import {expect} from 'chai'; +import { describe } from 'mocha'; +import { expect } from 'chai'; -import {Clock} from '../../src/timing/Clock'; -import {Scheduler} from "../../src/timing/Scheduler"; +import { Clock } from '../../src/timing/Clock'; +import { Scheduler } from "../../src/timing/Scheduler"; -import {ClockStub} from '../stubs/ClockStub'; +import { ClockStub } from '../stubs/ClockStub'; describe('Scheduler', () => { describe('#scheduleOperation()', () => { - it('schedules an operation that has not already been scheduled', () => { - const clock = new Clock(); - const scheduler = new Scheduler(clock); - const result = scheduler.scheduleOperation('someTag', 0, () => {}); - expect(result).to.be.true; - }); - - it('does not schedule an operation that has already been scheduled', () => { - const clock = new Clock(); - const scheduler = new Scheduler(clock); - scheduler.scheduleOperation('someTag', 0, () => {}); - const result = scheduler.scheduleOperation('someTag', 0, () => {}); - expect(result).to.be.false; - }); + it('schedules an operation that has not already been scheduled', () => { + const clock = new Clock(); + const scheduler = new Scheduler(clock); + const result = scheduler.scheduleOperation('someTag', 0, () => { }); + expect(result).to.be.true; + }); + + it('does not schedule an operation that has already been scheduled', () => { + const clock = new Clock(); + const scheduler = new Scheduler(clock); + scheduler.scheduleOperation('someTag', 0, () => { }); + const result = scheduler.scheduleOperation('someTag', 0, () => { }); + expect(result).to.be.false; + }); it('allows an operation to be re-scheduled from inside the handler of that operation', () => { const clock = new ClockStub(); @@ -30,57 +30,57 @@ describe('Scheduler', () => { let operationExecuted = false; // Schedule an initial operation... scheduler.scheduleOperation('someTag', 0, () => { - // ...which, when due, will schedule another operation with the same tag - scheduler.scheduleOperation('someTag', 0, () => { - operationExecuted = true; - }); + // ...which, when due, will schedule another operation with the same tag + scheduler.scheduleOperation('someTag', 0, () => { + operationExecuted = true; + }); }); scheduler.executeDueOperations(); // Execute initial operation. scheduler.executeDueOperations(); // Execute new operation added during first operation. expect(operationExecuted).to.be.true; }); + }); - describe('#executeDueOperations()', () => { - it('does not execute scheduled operations that are not yet due', () => { - const clock = new ClockStub(); - const scheduler = new Scheduler(clock); - - let operationExecuted = false; - scheduler.scheduleOperation('someTag', 5000, () => { - operationExecuted = true; - }); - scheduler.executeDueOperations(); + describe('#executeDueOperations()', () => { + it('does not execute scheduled operations that are not yet due', () => { + const clock = new ClockStub(); + const scheduler = new Scheduler(clock); - expect(operationExecuted).to.be.false; + let operationExecuted = false; + scheduler.scheduleOperation('someTag', 5000, () => { + operationExecuted = true; }); + scheduler.executeDueOperations(); - it('executes scheduled operations that are exactly due', () => { - const clock = new ClockStub(); - const scheduler = new Scheduler(clock); + expect(operationExecuted).to.be.false; + }); - let operationExecuted = false; - scheduler.scheduleOperation('someTag', 0, () => { - operationExecuted = true; - }); - scheduler.executeDueOperations(); + it('executes scheduled operations that are exactly due', () => { + const clock = new ClockStub(); + const scheduler = new Scheduler(clock); - expect(operationExecuted).to.be.true; + let operationExecuted = false; + scheduler.scheduleOperation('someTag', 0, () => { + operationExecuted = true; }); + scheduler.executeDueOperations(); - it ('executes scheduled operations that are past due', () => { - const clock = new ClockStub(); - const scheduler = new Scheduler(clock); + expect(operationExecuted).to.be.true; + }); - let operationExecuted = false; - scheduler.scheduleOperation('someTag', 0, () => { - operationExecuted = true; - }); - clock.addSeconds(5); - scheduler.executeDueOperations(); + it('executes scheduled operations that are past due', () => { + const clock = new ClockStub(); + const scheduler = new Scheduler(clock); - expect(operationExecuted).to.be.true; + let operationExecuted = false; + scheduler.scheduleOperation('someTag', 0, () => { + operationExecuted = true; }); + clock.addSeconds(5); + scheduler.executeDueOperations(); + + expect(operationExecuted).to.be.true; }); }); }); From 614726b2e8f44a71c9cc2ed59f9a9704acc5afef Mon Sep 17 00:00:00 2001 From: Paul Grebenc Date: Tue, 14 Jan 2025 21:47:06 -0500 Subject: [PATCH 04/32] Basic periodic single enemy swoops are functional. --- src/enemies/Zagger.ts | 88 ++++++++++++++++++++++------------------- src/waves/ZigZagWave.ts | 17 ++++++++ 2 files changed, 65 insertions(+), 40 deletions(-) diff --git a/src/enemies/Zagger.ts b/src/enemies/Zagger.ts index 0ba0c38..9708c82 100644 --- a/src/enemies/Zagger.ts +++ b/src/enemies/Zagger.ts @@ -19,16 +19,19 @@ import {ScheduledAction} from '../paths/ScheduledAction'; import {Scheduler} from '../timing/Scheduler'; import {SplinePath} from '../paths/SplinePath'; import {World} from "../World"; +import { State } from "pixi.js"; export class Zagger extends Enemy { public static readonly InitialHealth: number = 1; private readonly _scheduler: Scheduler; private readonly _hitArbiter: HitArbiter; + private readonly _homePosition: Point; private _currentFrame: number = 0; private _currentPath!: PathEntry[]; private _currentPathTemplate!: PathEntry[]; private _pathPosition!: number; + private _state: Zagger.State; constructor(audioPlayer: AudioPlayer, world: World, clock: Clock, startingPoint: Point, homePosition: Point) { super(audioPlayer, world, startingPoint, Zagger.InitialHealth); @@ -36,8 +39,10 @@ export class Zagger extends Enemy { this._scheduler = new Scheduler(clock); this._hitArbiter = new HitArbiter(this); + this._homePosition = homePosition; this.prepareEntryPath(homePosition); + this._state = Zagger.State.Entering; this.advanceCurrentFrame(); } @@ -106,46 +111,47 @@ export class Zagger extends Enemy { this._world.addActor(bomb); } + public swoop(): void { + // Determine our downward swooping path. + const lowestPoint = new Point(200, 600); // Not really what we want... + const linePath = new LinePath(this._location, lowestPoint, []); + this._currentPath = linePath.getPath(100); + this._pathPosition = 0; + this._state = Zagger.State.Swooping; + } + private step(): void { // Choose the next path to follow once we've reach the end of the current path. if (this._pathPosition >= this._currentPath.length) { + + switch (this._state) { + case Zagger.State.Entering: + this._state = Zagger.State.Waiting; + break; + case Zagger.State.Swooping: + this._scheduler.scheduleOperation( + 'enter', + 3000, + () => { + // Determine our path back to our home position after. + const worldDimensions = this._world.dimensions; + const zaggerStartingPoint = new Point( + Math.floor(random(0, worldDimensions.width-50)), + -20 + ); + const linePath = new LinePath(zaggerStartingPoint, this._homePosition, []); + this._currentPath = linePath.getPath(20); + this._pathPosition = 0; + this._state = Zagger.State.Entering; + } + ); + this._state = Zagger.State.Waiting; + break; + } + + // Possibly at some random chance drop bombs? + return; - //let nextPath: PathEntry[]; - - // What do we do once we reach home... - // if (this._currentPathTemplate === Zagger._floatAroundPathTemplate) { - // if (random(0, 1) > 0.5) { - // if (this._location.x < this._world.dimensions.width / 2) { - // nextPath = Zagger._flyRightPathTemplate; - // } - // else { - // nextPath = Zagger._flyLeftPathTemplate; - // } - // } - // else { - // if (this._location.y < this._world.dimensions.height / 2) { - // if (random(0, 1) > 0.5) { - // nextPath = Zagger._flyDownPathTemplate; - // } - // else { - // if (this._location.x < this._world.dimensions.width / 2) { - // nextPath = Zagger._diveRightPathTemplate; - // } - // else { - // nextPath = Zagger._diveLeftPathTemplate; - // } - // } - // } - // else { - // nextPath = Zagger._flyUpPathTemplate; - // } - // } - // } - // else { - // nextPath = Zagger._floatAroundPathTemplate; - // } - - // this.prepareNextPath(nextPath); } // Follow the current path. @@ -165,10 +171,12 @@ export class Zagger extends Enemy { this._currentPath = linePath.getPath(20); this._pathPosition = 0; } +} - private prepareNextPath(pathTemplate: PathEntry[]): void { - this._currentPathTemplate = pathTemplate; - this._currentPath = SplinePath.translatePath(pathTemplate, this._location.x, this._location.y); - this._pathPosition = 0; +export module Zagger { + export enum State { + Entering, + Swooping, + Waiting } } diff --git a/src/waves/ZigZagWave.ts b/src/waves/ZigZagWave.ts index 2c49260..71fdcab 100644 --- a/src/waves/ZigZagWave.ts +++ b/src/waves/ZigZagWave.ts @@ -44,9 +44,26 @@ export class ZigZagWave implements Wave { debug('ZigZagWave.tick'); this._scheduler.executeDueOperations(); + + if (this._numberOfEnemiesLeftToDeploy == 0) { + this._scheduler.scheduleOperation('next swoop', 0, () => this.scheduleNextSwoop()); + } + } + + scheduleNextSwoop() : void { + if (this._world.activeEnemies.length > 0) { + const timeTillSwoop = random(1000, 5000); + const zagger = this._world.activeEnemies[Math.floor(random(0, this._world.activeEnemies.length))] as Zagger; + this._scheduler.scheduleOperation( + 'next swoop', + timeTillSwoop, + () => zagger.swoop() + ); + } } deployZagger(): void { + //TODO: Don't think we need this if statement if we stop re-scheduling... if (this._numberOfEnemiesLeftToDeploy > 0) { const worldDimensions = this._world.dimensions; const zaggerStartingPoint = new Point( From b276e498c32f412e4be7c9080dde99f74715ef4f Mon Sep 17 00:00:00 2001 From: Paul Grebenc Date: Tue, 14 Jan 2025 21:52:34 -0500 Subject: [PATCH 05/32] Cleanup. --- src/waves/ZigZagWave.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/waves/ZigZagWave.ts b/src/waves/ZigZagWave.ts index 71fdcab..ac173b4 100644 --- a/src/waves/ZigZagWave.ts +++ b/src/waves/ZigZagWave.ts @@ -14,7 +14,6 @@ export class ZigZagWave implements Wave { private readonly _audioPlayer: AudioPlayer; private readonly _world: World; private readonly _clock: Clock; - private _addNextEnemyAt: Date = new Date(); private _numberOfEnemiesLeftToDeploy: number = 15; private readonly _scheduler: Scheduler; @@ -63,7 +62,6 @@ export class ZigZagWave implements Wave { } deployZagger(): void { - //TODO: Don't think we need this if statement if we stop re-scheduling... if (this._numberOfEnemiesLeftToDeploy > 0) { const worldDimensions = this._world.dimensions; const zaggerStartingPoint = new Point( From e38caa6852476a136057821da8ba40adb4f3a93a Mon Sep 17 00:00:00 2001 From: Paul Grebenc Date: Wed, 15 Jan 2025 10:16:00 -0500 Subject: [PATCH 06/32] Fix lint issues. --- src/enemies/Zagger.ts | 1 - src/waves/ZigZagWave.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/enemies/Zagger.ts b/src/enemies/Zagger.ts index 9708c82..b3ede16 100644 --- a/src/enemies/Zagger.ts +++ b/src/enemies/Zagger.ts @@ -19,7 +19,6 @@ import {ScheduledAction} from '../paths/ScheduledAction'; import {Scheduler} from '../timing/Scheduler'; import {SplinePath} from '../paths/SplinePath'; import {World} from "../World"; -import { State } from "pixi.js"; export class Zagger extends Enemy { public static readonly InitialHealth: number = 1; diff --git a/src/waves/ZigZagWave.ts b/src/waves/ZigZagWave.ts index ac173b4..04b051a 100644 --- a/src/waves/ZigZagWave.ts +++ b/src/waves/ZigZagWave.ts @@ -44,7 +44,7 @@ export class ZigZagWave implements Wave { this._scheduler.executeDueOperations(); - if (this._numberOfEnemiesLeftToDeploy == 0) { + if (this._numberOfEnemiesLeftToDeploy === 0) { this._scheduler.scheduleOperation('next swoop', 0, () => this.scheduleNextSwoop()); } } From 06c88125acb967cfe6b9b508adfd444682af4a28 Mon Sep 17 00:00:00 2001 From: Paul Grebenc Date: Sat, 18 Jan 2025 18:45:28 -0500 Subject: [PATCH 07/32] Don't tell a Zagger to swoop if that's what it's doing now. --- src/enemies/Zagger.ts | 6 +++++- src/waves/ZigZagWave.ts | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/enemies/Zagger.ts b/src/enemies/Zagger.ts index b3ede16..d2b93fb 100644 --- a/src/enemies/Zagger.ts +++ b/src/enemies/Zagger.ts @@ -45,6 +45,10 @@ export class Zagger extends Enemy { this.advanceCurrentFrame(); } + get state(): Zagger.State { + return this._state; + } + get explosionProperties(): ExplosionProperties { return new ExplosionProperties( 'saucer_explosion', @@ -112,7 +116,7 @@ export class Zagger extends Enemy { public swoop(): void { // Determine our downward swooping path. - const lowestPoint = new Point(200, 600); // Not really what we want... + const lowestPoint = new Point(Math.floor(random(10, 430)), 650); const linePath = new LinePath(this._location, lowestPoint, []); this._currentPath = linePath.getPath(100); this._pathPosition = 0; diff --git a/src/waves/ZigZagWave.ts b/src/waves/ZigZagWave.ts index 04b051a..a347922 100644 --- a/src/waves/ZigZagWave.ts +++ b/src/waves/ZigZagWave.ts @@ -52,7 +52,8 @@ export class ZigZagWave implements Wave { scheduleNextSwoop() : void { if (this._world.activeEnemies.length > 0) { const timeTillSwoop = random(1000, 5000); - const zagger = this._world.activeEnemies[Math.floor(random(0, this._world.activeEnemies.length))] as Zagger; + const inactiveZaggers = this._world.activeEnemies.filter(enemy => (enemy as Zagger).state !== Zagger.State.Swooping); + const zagger = inactiveZaggers[random(inactiveZaggers.length-1)] as Zagger; this._scheduler.scheduleOperation( 'next swoop', timeTillSwoop, From dc60dfda982d61f3b51445632c0f61eb914bf576 Mon Sep 17 00:00:00 2001 From: Paul Grebenc Date: Sat, 18 Jan 2025 19:02:00 -0500 Subject: [PATCH 08/32] Add ability for Zaggers to swoop and return. --- src/enemies/Zagger.ts | 27 +++++++++++++++++++++------ src/waves/ZigZagWave.ts | 4 ++-- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/enemies/Zagger.ts b/src/enemies/Zagger.ts index d2b93fb..0273c81 100644 --- a/src/enemies/Zagger.ts +++ b/src/enemies/Zagger.ts @@ -115,12 +115,23 @@ export class Zagger extends Enemy { } public swoop(): void { - // Determine our downward swooping path. - const lowestPoint = new Point(Math.floor(random(10, 430)), 650); - const linePath = new LinePath(this._location, lowestPoint, []); - this._currentPath = linePath.getPath(100); - this._pathPosition = 0; - this._state = Zagger.State.Swooping; + if (random(0, 1) === 0) { + // Swoop down and off the screen. + const lowestPoint = new Point(Math.floor(random(10, 430)), 650); + const linePath = new LinePath(this._location, lowestPoint, []); + this._currentPath = linePath.getPath(100); + this._pathPosition = 0; + this._state = Zagger.State.Swooping; + } + else { + // Swwop and return to home. + const lowestPoint = new Point(Math.floor(random(10, 430)), 580); + const swoopDownPath = new LinePath(this._location, lowestPoint, []); + const swoopReturnPath = new LinePath(lowestPoint, this._homePosition, []); + this._currentPath = swoopDownPath.getPath(100).concat(swoopReturnPath.getPath(100)); + this._pathPosition = 0; + this._state = Zagger.State.SwoopAndReturn; + } } private step(): void { @@ -131,6 +142,9 @@ export class Zagger extends Enemy { case Zagger.State.Entering: this._state = Zagger.State.Waiting; break; + case Zagger.State.SwoopAndReturn: + this._state = Zagger.State.Waiting; + break; case Zagger.State.Swooping: this._scheduler.scheduleOperation( 'enter', @@ -179,6 +193,7 @@ export class Zagger extends Enemy { export module Zagger { export enum State { Entering, + SwoopAndReturn, Swooping, Waiting } diff --git a/src/waves/ZigZagWave.ts b/src/waves/ZigZagWave.ts index a347922..a51f659 100644 --- a/src/waves/ZigZagWave.ts +++ b/src/waves/ZigZagWave.ts @@ -52,8 +52,8 @@ export class ZigZagWave implements Wave { scheduleNextSwoop() : void { if (this._world.activeEnemies.length > 0) { const timeTillSwoop = random(1000, 5000); - const inactiveZaggers = this._world.activeEnemies.filter(enemy => (enemy as Zagger).state !== Zagger.State.Swooping); - const zagger = inactiveZaggers[random(inactiveZaggers.length-1)] as Zagger; + const waitingZaggers = this._world.activeEnemies.filter(enemy => (enemy as Zagger).state === Zagger.State.Waiting); + const zagger = waitingZaggers[random(waitingZaggers.length-1)] as Zagger; this._scheduler.scheduleOperation( 'next swoop', timeTillSwoop, From b52da4a812bb19024cf56db07bac68f31cbf56f1 Mon Sep 17 00:00:00 2001 From: Paul Grebenc Date: Sat, 18 Jan 2025 19:19:19 -0500 Subject: [PATCH 09/32] Zagger can change direction once on swoop, but speed not consistent. --- src/enemies/Zagger.ts | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/enemies/Zagger.ts b/src/enemies/Zagger.ts index 0273c81..ca829a6 100644 --- a/src/enemies/Zagger.ts +++ b/src/enemies/Zagger.ts @@ -117,11 +117,24 @@ export class Zagger extends Enemy { public swoop(): void { if (random(0, 1) === 0) { // Swoop down and off the screen. - const lowestPoint = new Point(Math.floor(random(10, 430)), 650); - const linePath = new LinePath(this._location, lowestPoint, []); - this._currentPath = linePath.getPath(100); - this._pathPosition = 0; - this._state = Zagger.State.Swooping; + const lowestPoint = new Point(Math.floor(random(10, 430)), 660); + const turns = random(0, 1); + switch (turns) { + case 0: + const linePath = new LinePath(this._location, lowestPoint, []); + this._currentPath = linePath.getPath(100); + this._pathPosition = 0; + this._state = Zagger.State.Swooping; + break; + case 1: + const midPoint = new Point(Math.floor(random(10, 430)), random(300, 500)); + const path1 = new LinePath(this._location, midPoint, []); + const path2 = new LinePath(midPoint, lowestPoint, []); + this._currentPath = path1.getPath(50).concat(path2.getPath(50)); //TODO: LinePath should support multiple points. + this._pathPosition = 0; + this._state = Zagger.State.Swooping; + break; + } } else { // Swwop and return to home. From 0269ea8cae56176ee522a809174749f81be07b93 Mon Sep 17 00:00:00 2001 From: Paul Grebenc Date: Thu, 23 Jan 2025 20:47:15 -0500 Subject: [PATCH 10/32] Settings for debugging Mocha tests. --- .vscode/settings.json | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..460ce4c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "mochaExplorer.files": "test/**/*.spec.[jt]s", + "mochaExplorer.require": "ts-node/register" +} \ No newline at end of file From 54790a4023cf7f57347fc21dceb42f0d11e87b28 Mon Sep 17 00:00:00 2001 From: Paul Grebenc Date: Sat, 25 Jan 2025 20:09:36 -0500 Subject: [PATCH 11/32] Added LineSegmentPath. --- src/Point.ts | 4 + src/paths/LineSegmentPath.ts | 65 +++++++++++++ test/paths/LineSegmentPath.spec.ts | 151 +++++++++++++++++++++++++++++ 3 files changed, 220 insertions(+) create mode 100644 src/paths/LineSegmentPath.ts create mode 100644 test/paths/LineSegmentPath.spec.ts diff --git a/src/Point.ts b/src/Point.ts index a360fd5..ddfbb5c 100644 --- a/src/Point.ts +++ b/src/Point.ts @@ -43,6 +43,10 @@ export class Point { return new Point(this._x + count, this._y); } + equals(other: Point): boolean { + return this._x === other._x && this._y === other._y; + } + toString(): string { return 'Point(x=' + this._x + ', y=' + this._y + ')'; } diff --git a/src/paths/LineSegmentPath.ts b/src/paths/LineSegmentPath.ts new file mode 100644 index 0000000..ee6ab2b --- /dev/null +++ b/src/paths/LineSegmentPath.ts @@ -0,0 +1,65 @@ +import Debug from "debug"; +import {Point} from "../Point"; +import {ScheduledAction} from "./ScheduledAction"; +import {PathEntry} from "./PathEntry"; +import {LinePath} from "./LinePath"; + +const debug = Debug("Blaster:Paths:LinePath"); + +export class LineSegmentPath { + private readonly _points: Point[]; + private readonly _scheduledActions: ScheduledAction[]; + + constructor(points: Point[], scheduledActions: ScheduledAction[]) { + this._points = points; + this._scheduledActions = scheduledActions; + } + + private getSegmentLength(point1: Point, point2: Point): number { + const dx = point2.x - point1.x; + const dy = point2.y - point1.y; + return Math.sqrt(dx * dx + dy * dy); + } + + getPath(numberOfSteps: number): PathEntry[] { + const path: PathEntry[] = []; + + const stepSize = 1.0 / numberOfSteps; + + var totalLength: number = 0; + for (let i = 0; i < this._points.length - 1; i++) { + totalLength += this.getSegmentLength(this._points[i], this._points[i + 1]); + } + + // foreach segment + // determine ratio of this segment to total length + // create line path for this segment with number of points based on ratio + // add path to a total path + for (let i = 0; i < this._points.length - 1; i++) { + const segmentLength = this.getSegmentLength(this._points[i], this._points[i + 1]); + const segmentNumberOfSteps = Math.round(segmentLength / totalLength * numberOfSteps); + + const linePath = new LinePath(this._points[i], this._points[i + 1], []); + const segmentPath = linePath.getPath(segmentNumberOfSteps); + + // The end of each segment is the same as the start of the next segment, so we don't want to duplicate it. + path.pop(); + path.push(...segmentPath); + } + + if (this._scheduledActions) { + for (const scheduledAction of this._scheduledActions) { + // Create the new action entry. + const pathEntry = new PathEntry(scheduledAction.action, null); + + // Figure out what offset to include it at. + const stepPosition = Math.floor(numberOfSteps * scheduledAction.when); + + // Insert it at that position. + path.splice(stepPosition, 0, pathEntry); + } + } + + return path; + }; +} diff --git a/test/paths/LineSegmentPath.spec.ts b/test/paths/LineSegmentPath.spec.ts new file mode 100644 index 0000000..151e5ba --- /dev/null +++ b/test/paths/LineSegmentPath.spec.ts @@ -0,0 +1,151 @@ +import {describe} from 'mocha'; +import {expect} from 'chai'; + +import {every} from 'underscore'; + +import {LineSegmentPath} from "../../src/paths/LineSegmentPath"; +import {Point} from "../../src/Point"; +import {ScheduledAction} from "../../src/paths/ScheduledAction"; +import {PathEntry} from "../../src/paths/PathEntry"; +import {PathAction} from "../../src/paths/PathAction"; + +describe('LineSegmentPath', () => { + describe('#getPath() [two segment]', () => { + it('creates a path with the specified number of steps', () => { + const linePath = new LineSegmentPath([new Point(0.0, 0.0), new Point(100.0, 100.0), new Point(200.0, 0.0)], []); + + const numberOfSteps = 10; + const path = linePath.getPath(numberOfSteps); + + // A given number of steps require one additional point (fencepost analogy). + expect(path.length).to.be.equal(numberOfSteps + 1); + }); + + it('creates a path containing only movements when no other actions are specified in the template', () => { + const linePath = new LineSegmentPath([new Point(0.0, 0.0), new Point(100.0, 100.0), new Point(200.0, 0.0)], []); + + const path = linePath.getPath(10); + + // All steps that are movements. + const movements = path.filter((value: PathEntry): boolean => { return value.action === PathAction.Move }); + + expect(movements.length).to.be.equal(path.length); + }); + + it('creates paths that begin and terminate at the first and last provided points', () => { + const linePath = new LineSegmentPath([new Point(0.0, 0.0), new Point(10.0, 30.0), new Point(20.0, 60.0)], []); + + const path = linePath.getPath(10); + + expect(Math.round(path[0].location!.x)).to.be.eql(0.0); + expect(Math.round(path[0].location!.y)).to.be.eql(0.0); + + expect(Math.round(path[10].location!.x)).to.be.closeTo(20.0, 0.01); + expect(Math.round(path[10].location!.y)).to.be.closeTo(60.0, 0.01); + }); + + it('inserts non-movement actions at specified positions along the path', () => { + const linePath = new LineSegmentPath([new Point(0.0, 0.0), new Point(100.0, 100.0), new Point(200.0, 200.0)], [new ScheduledAction(0.50, PathAction.Fire)]); + + const path = linePath.getPath(10); + + // One extra step for the fire action. + expect(path.length).to.be.equal(12); + + expect(path[5].action).to.be.equal(PathAction.Fire); + }); + + it('generates coordinates that include specified points', () => { + const points = [new Point(0.0, 0.0), new Point(100.0, 100.0), new Point(200.0, 200.0)]; + const linePath = new LineSegmentPath(points, []); + + const path = linePath.getPath(10); + + for (const point of points) { + expect(path.filter((value: PathEntry): boolean => { return value.location!.equals(point); }).length).to.be.equal(1); + } + }); + + it('only generates coordinates between the start and end of each pair of points', () => { + const linePath = new LineSegmentPath([new Point(0.0, 0.0), new Point(10.0, 0.0), new Point(10.0, 10.0)], []); + + const path = linePath.getPath(10); + + path.slice(0, 4).forEach((point: PathEntry): void => { + expect(point.location!.x).to.be.lessThanOrEqual(10.0); + expect(point.location!.y).to.be.closeTo(0.0, 0.01); + }); + path.slice(5, 10).forEach((point: PathEntry): void => { + expect(point.location!.x).to.be.closeTo(10.0, 0.01); + expect(point.location!.y).to.be.lessThanOrEqual(10.0); + }); + }); + }); + + describe('#getPath() [single segment]', () => { + it ('creates a path with the specified number of steps', () => { + const linePath = new LineSegmentPath([new Point(0.0, 0.0), new Point(100.0, 100.0)], []); + + const numberOfSteps = 10; + const path = linePath.getPath(numberOfSteps); + + // A given number of steps require one additional point (fencepost analogy). + expect(path.length).to.be.equal(numberOfSteps + 1); + }); + + it ('creates a path containing only movements when no other actions are specified in the template', () => { + const linePath = new LineSegmentPath([new Point(0.0, 0.0), new Point(100.0, 100.0)], []); + + const path = linePath.getPath(10); + + // All steps that are movements. + const movements = path.filter((value: PathEntry): boolean => { return value.action === PathAction.Move }); + + expect(movements.length).to.be.equal(path.length); + }); + + it('creates paths that begin and terminate at the first and last provided points', () => { + const linePath = new LineSegmentPath([new Point(0.0, 0.0), new Point(10.0, 30.0)], []); + + const path = linePath.getPath(10); + + expect(Math.round(path[0].location!.x)).to.be.eql(0.0); + expect(Math.round(path[0].location!.y)).to.be.eql(0.0); + + expect(Math.round(path[10].location!.x)).to.be.closeTo(10.0, 0.01); + expect(Math.round(path[10].location!.y)).to.be.closeTo(30.0, 0.01); + }); + + it('inserts non-movement actions at specified positions along the path', () => { + const linePath = new LineSegmentPath( + [new Point(0.0, 0.0), new Point(100.0, 100.0)], + [new ScheduledAction(0.50, PathAction.Fire)]); + + const path = linePath.getPath(10); + + // One extra step for the fire action. + expect(path.length).to.be.equal(12); + + expect(path[5].action).to.be.equal(PathAction.Fire); + }); + + it('only generates coordinates between the start and end points', () => { + const linePath = new LineSegmentPath([new Point(10.0, 50.0), new Point(40.0, 90.0)], []); + + const path = linePath.getPath(10); + + expect( + every( + path.map((point: PathEntry): number => { return point.location!.x }), + (value: number): boolean => { return (value >= 10.0) && (value <= 40.0); } + ) + ).to.be.true; + expect( + every( + path.map((point: PathEntry): number => { return point.location!.y }), + (value: number): boolean => { return (value >= 50.0) && (value <= 90.0); } + ) + ).to.be.true; + }); + }); +}); From d155458b09b2bad0bc8322c3d46bb5847949337b Mon Sep 17 00:00:00 2001 From: Paul Grebenc Date: Sat, 25 Jan 2025 20:30:33 -0500 Subject: [PATCH 12/32] Fix wrong keyword. --- src/paths/LineSegmentPath.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/paths/LineSegmentPath.ts b/src/paths/LineSegmentPath.ts index ee6ab2b..6b47541 100644 --- a/src/paths/LineSegmentPath.ts +++ b/src/paths/LineSegmentPath.ts @@ -26,7 +26,7 @@ export class LineSegmentPath { const stepSize = 1.0 / numberOfSteps; - var totalLength: number = 0; + let totalLength: number = 0; for (let i = 0; i < this._points.length - 1; i++) { totalLength += this.getSegmentLength(this._points[i], this._points[i + 1]); } From 7fde125c863f2096e61bd3ea9812b4c79ffd8033 Mon Sep 17 00:00:00 2001 From: Paul Grebenc Date: Sat, 3 May 2025 20:38:55 -0400 Subject: [PATCH 13/32] Added support for building line paths based on a fixed speed. --- src/paths/LinePath.ts | 12 ++++++++++- src/paths/LineSegmentPath.ts | 14 ++++++++++-- test/paths/LinePath.spec.ts | 30 ++++++++++++++++++++------ test/paths/LineSegmentPath.spec.ts | 34 +++++++++++++++++++----------- 4 files changed, 69 insertions(+), 21 deletions(-) diff --git a/src/paths/LinePath.ts b/src/paths/LinePath.ts index d26f237..eaa60ed 100644 --- a/src/paths/LinePath.ts +++ b/src/paths/LinePath.ts @@ -17,7 +17,17 @@ export class LinePath { this._scheduledActions = scheduledActions; } - getPath(numberOfSteps: number): PathEntry[] { + getPathForSpeed(speed: number): PathEntry[] { + var lineLength = Math.sqrt( + Math.pow(this._end.x - this._start.x, 2) + + Math.pow(this._end.y - this._start.y, 2) + ); + var numberOfSteps = Math.max(Math.floor(lineLength / speed), 1); + + return this.getPathForSteps(numberOfSteps); + } + + getPathForSteps(numberOfSteps: number): PathEntry[] { const path: PathEntry[] = []; const stepSize = 1.0 / numberOfSteps; diff --git a/src/paths/LineSegmentPath.ts b/src/paths/LineSegmentPath.ts index 6b47541..ca8fd32 100644 --- a/src/paths/LineSegmentPath.ts +++ b/src/paths/LineSegmentPath.ts @@ -21,7 +21,17 @@ export class LineSegmentPath { return Math.sqrt(dx * dx + dy * dy); } - getPath(numberOfSteps: number): PathEntry[] { + getPathForSpeed(speed: number): PathEntry[] { + let lineLength = 0.0; + for (let i = 0; i < this._points.length - 1; i++) { + lineLength += this.getSegmentLength(this._points[i], this._points[i + 1]); + } + const numberOfSteps = Math.max(Math.floor(lineLength / speed), 1); + + return this.getPathForSteps(numberOfSteps); + } + + getPathForSteps(numberOfSteps: number): PathEntry[] { const path: PathEntry[] = []; const stepSize = 1.0 / numberOfSteps; @@ -40,7 +50,7 @@ export class LineSegmentPath { const segmentNumberOfSteps = Math.round(segmentLength / totalLength * numberOfSteps); const linePath = new LinePath(this._points[i], this._points[i + 1], []); - const segmentPath = linePath.getPath(segmentNumberOfSteps); + const segmentPath = linePath.getPathForSteps(segmentNumberOfSteps); // The end of each segment is the same as the start of the next segment, so we don't want to duplicate it. path.pop(); diff --git a/test/paths/LinePath.spec.ts b/test/paths/LinePath.spec.ts index e9291ae..8b2a6a2 100644 --- a/test/paths/LinePath.spec.ts +++ b/test/paths/LinePath.spec.ts @@ -10,12 +10,30 @@ import {PathEntry} from "../../src/paths/PathEntry"; import {PathAction} from "../../src/paths/PathAction"; describe('LinePath', () => { - describe('#getPath()', () => { + describe('getPathForSpeed()', () => { + it('creates a path with the specified speed', () => { + const linePath = new LinePath(new Point(0.0, 0.0), new Point(100.0, 100.0), []); + + const path = linePath.getPathForSpeed(10.0); + + expect(path.length).to.be.equal(15); + }); + + it('creates a path with one step if speed greater than distance', () => { + const linePath = new LinePath(new Point(0.0, 0.0), new Point(5.0, 5.0), []); + + const path = linePath.getPathForSpeed(100.0); + + expect(path.length).to.be.equal(2); + }); + }); + + describe('#getPathForSteps()', () => { it ('creates a path with the specified number of steps', () => { const linePath = new LinePath(new Point(0.0, 0.0), new Point(100.0, 100.0), []); const numberOfSteps = 10; - const path = linePath.getPath(numberOfSteps); + const path = linePath.getPathForSteps(numberOfSteps); // A given number of steps require one additional point (fencepost analogy). expect(path.length).to.be.equal(numberOfSteps + 1); @@ -24,7 +42,7 @@ describe('LinePath', () => { it ('creates a path containing only movements when no other actions are specified in the template', () => { const linePath = new LinePath(new Point(0.0, 0.0), new Point(100.0, 100.0), []); - const path = linePath.getPath(10); + const path = linePath.getPathForSteps(10); // All steps that are movements. const movements = path.filter((value: PathEntry): boolean => { return value.action === PathAction.Move }); @@ -35,7 +53,7 @@ describe('LinePath', () => { it('creates paths that begin and terminate at the first and last provided points', () => { const linePath = new LinePath(new Point(0.0, 0.0), new Point(10.0, 30.0), []); - const path = linePath.getPath(10); + const path = linePath.getPathForSteps(10); expect(Math.round(path[0].location!.x)).to.be.eql(0.0); expect(Math.round(path[0].location!.y)).to.be.eql(0.0); @@ -50,7 +68,7 @@ describe('LinePath', () => { new Point(100.0, 100.0), [new ScheduledAction(0.50, PathAction.Fire)]); - const path = linePath.getPath(10); + const path = linePath.getPathForSteps(10); // One extra step for the fire action. expect(path.length).to.be.equal(12); @@ -61,7 +79,7 @@ describe('LinePath', () => { it('only generates coordinates between the start and end points', () => { const linePath = new LinePath(new Point(10.0, 50.0), new Point(40.0, 90.0), []); - const path = linePath.getPath(10); + const path = linePath.getPathForSteps(10); expect( every( diff --git a/test/paths/LineSegmentPath.spec.ts b/test/paths/LineSegmentPath.spec.ts index 151e5ba..2fd6cce 100644 --- a/test/paths/LineSegmentPath.spec.ts +++ b/test/paths/LineSegmentPath.spec.ts @@ -10,12 +10,22 @@ import {PathEntry} from "../../src/paths/PathEntry"; import {PathAction} from "../../src/paths/PathAction"; describe('LineSegmentPath', () => { - describe('#getPath() [two segment]', () => { + describe('#getPathForSpeed() [two segment]', () => { + it('creates a path with specified speed', () => { + const linePath = new LineSegmentPath([new Point(0.0, 0.0), new Point(100.0, 100.0), new Point(200.0, 0.0)], []); + + const path = linePath.getPathForSpeed(10); + + expect(path.length).to.be.equal(29); + }); + }); + + describe('#getPathForSteps() [two segment]', () => { it('creates a path with the specified number of steps', () => { const linePath = new LineSegmentPath([new Point(0.0, 0.0), new Point(100.0, 100.0), new Point(200.0, 0.0)], []); const numberOfSteps = 10; - const path = linePath.getPath(numberOfSteps); + const path = linePath.getPathForSteps(numberOfSteps); // A given number of steps require one additional point (fencepost analogy). expect(path.length).to.be.equal(numberOfSteps + 1); @@ -24,7 +34,7 @@ describe('LineSegmentPath', () => { it('creates a path containing only movements when no other actions are specified in the template', () => { const linePath = new LineSegmentPath([new Point(0.0, 0.0), new Point(100.0, 100.0), new Point(200.0, 0.0)], []); - const path = linePath.getPath(10); + const path = linePath.getPathForSteps(10); // All steps that are movements. const movements = path.filter((value: PathEntry): boolean => { return value.action === PathAction.Move }); @@ -35,7 +45,7 @@ describe('LineSegmentPath', () => { it('creates paths that begin and terminate at the first and last provided points', () => { const linePath = new LineSegmentPath([new Point(0.0, 0.0), new Point(10.0, 30.0), new Point(20.0, 60.0)], []); - const path = linePath.getPath(10); + const path = linePath.getPathForSteps(10); expect(Math.round(path[0].location!.x)).to.be.eql(0.0); expect(Math.round(path[0].location!.y)).to.be.eql(0.0); @@ -47,7 +57,7 @@ describe('LineSegmentPath', () => { it('inserts non-movement actions at specified positions along the path', () => { const linePath = new LineSegmentPath([new Point(0.0, 0.0), new Point(100.0, 100.0), new Point(200.0, 200.0)], [new ScheduledAction(0.50, PathAction.Fire)]); - const path = linePath.getPath(10); + const path = linePath.getPathForSteps(10); // One extra step for the fire action. expect(path.length).to.be.equal(12); @@ -59,7 +69,7 @@ describe('LineSegmentPath', () => { const points = [new Point(0.0, 0.0), new Point(100.0, 100.0), new Point(200.0, 200.0)]; const linePath = new LineSegmentPath(points, []); - const path = linePath.getPath(10); + const path = linePath.getPathForSteps(10); for (const point of points) { expect(path.filter((value: PathEntry): boolean => { return value.location!.equals(point); }).length).to.be.equal(1); @@ -69,7 +79,7 @@ describe('LineSegmentPath', () => { it('only generates coordinates between the start and end of each pair of points', () => { const linePath = new LineSegmentPath([new Point(0.0, 0.0), new Point(10.0, 0.0), new Point(10.0, 10.0)], []); - const path = linePath.getPath(10); + const path = linePath.getPathForSteps(10); path.slice(0, 4).forEach((point: PathEntry): void => { expect(point.location!.x).to.be.lessThanOrEqual(10.0); @@ -87,7 +97,7 @@ describe('LineSegmentPath', () => { const linePath = new LineSegmentPath([new Point(0.0, 0.0), new Point(100.0, 100.0)], []); const numberOfSteps = 10; - const path = linePath.getPath(numberOfSteps); + const path = linePath.getPathForSteps(numberOfSteps); // A given number of steps require one additional point (fencepost analogy). expect(path.length).to.be.equal(numberOfSteps + 1); @@ -96,7 +106,7 @@ describe('LineSegmentPath', () => { it ('creates a path containing only movements when no other actions are specified in the template', () => { const linePath = new LineSegmentPath([new Point(0.0, 0.0), new Point(100.0, 100.0)], []); - const path = linePath.getPath(10); + const path = linePath.getPathForSteps(10); // All steps that are movements. const movements = path.filter((value: PathEntry): boolean => { return value.action === PathAction.Move }); @@ -107,7 +117,7 @@ describe('LineSegmentPath', () => { it('creates paths that begin and terminate at the first and last provided points', () => { const linePath = new LineSegmentPath([new Point(0.0, 0.0), new Point(10.0, 30.0)], []); - const path = linePath.getPath(10); + const path = linePath.getPathForSteps(10); expect(Math.round(path[0].location!.x)).to.be.eql(0.0); expect(Math.round(path[0].location!.y)).to.be.eql(0.0); @@ -121,7 +131,7 @@ describe('LineSegmentPath', () => { [new Point(0.0, 0.0), new Point(100.0, 100.0)], [new ScheduledAction(0.50, PathAction.Fire)]); - const path = linePath.getPath(10); + const path = linePath.getPathForSteps(10); // One extra step for the fire action. expect(path.length).to.be.equal(12); @@ -132,7 +142,7 @@ describe('LineSegmentPath', () => { it('only generates coordinates between the start and end points', () => { const linePath = new LineSegmentPath([new Point(10.0, 50.0), new Point(40.0, 90.0)], []); - const path = linePath.getPath(10); + const path = linePath.getPathForSteps(10); expect( every( From 48973db4ebcbf2a0704b0a27a69d4b4691b1cc17 Mon Sep 17 00:00:00 2001 From: "SINISTER\\paul" Date: Sun, 7 Sep 2025 17:13:01 -0400 Subject: [PATCH 14/32] Remove lint warning. --- src/paths/LinePath.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/paths/LinePath.ts b/src/paths/LinePath.ts index eaa60ed..9f659b2 100644 --- a/src/paths/LinePath.ts +++ b/src/paths/LinePath.ts @@ -18,11 +18,11 @@ export class LinePath { } getPathForSpeed(speed: number): PathEntry[] { - var lineLength = Math.sqrt( + const lineLength = Math.sqrt( Math.pow(this._end.x - this._start.x, 2) + Math.pow(this._end.y - this._start.y, 2) ); - var numberOfSteps = Math.max(Math.floor(lineLength / speed), 1); + const numberOfSteps = Math.max(Math.floor(lineLength / speed), 1); return this.getPathForSteps(numberOfSteps); } From edfe58a122166d3ea3f15eafceeed778d460eb93 Mon Sep 17 00:00:00 2001 From: "SINISTER\\paul" Date: Mon, 8 Sep 2025 19:44:03 -0400 Subject: [PATCH 15/32] Updating zagger. --- src/enemies/Zagger.ts | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/enemies/Zagger.ts b/src/enemies/Zagger.ts index ca829a6..4be8146 100644 --- a/src/enemies/Zagger.ts +++ b/src/enemies/Zagger.ts @@ -19,6 +19,7 @@ import {ScheduledAction} from '../paths/ScheduledAction'; import {Scheduler} from '../timing/Scheduler'; import {SplinePath} from '../paths/SplinePath'; import {World} from "../World"; +import { LineSegmentPath } from "../paths/LineSegmentPath"; export class Zagger extends Enemy { public static readonly InitialHealth: number = 1; @@ -118,30 +119,35 @@ export class Zagger extends Enemy { if (random(0, 1) === 0) { // Swoop down and off the screen. const lowestPoint = new Point(Math.floor(random(10, 430)), 660); - const turns = random(0, 1); + const turns = random(0, 2); switch (turns) { case 0: const linePath = new LinePath(this._location, lowestPoint, []); - this._currentPath = linePath.getPath(100); + this._currentPath = linePath.getPathForSpeed(5); this._pathPosition = 0; this._state = Zagger.State.Swooping; break; case 1: const midPoint = new Point(Math.floor(random(10, 430)), random(300, 500)); - const path1 = new LinePath(this._location, midPoint, []); - const path2 = new LinePath(midPoint, lowestPoint, []); - this._currentPath = path1.getPath(50).concat(path2.getPath(50)); //TODO: LinePath should support multiple points. + this._currentPath = new LineSegmentPath([this._location, midPoint, lowestPoint], []).getPathForSpeed(5); this._pathPosition = 0; this._state = Zagger.State.Swooping; break; - } + case 2: + const midPoint1 = new Point(Math.floor(random(10, 430)), random(300, 400)); + const midPoint2 = new Point(Math.floor(random(10, 430)), random(400, 500)); + this._currentPath = new LineSegmentPath([this._location, midPoint1, midPoint2, lowestPoint], []).getPathForSpeed(5); + this._pathPosition = 0; + this._state = Zagger.State.Swooping; + break; + } } else { // Swwop and return to home. const lowestPoint = new Point(Math.floor(random(10, 430)), 580); const swoopDownPath = new LinePath(this._location, lowestPoint, []); const swoopReturnPath = new LinePath(lowestPoint, this._homePosition, []); - this._currentPath = swoopDownPath.getPath(100).concat(swoopReturnPath.getPath(100)); + this._currentPath = swoopDownPath.getPathForSpeed(5).concat(swoopReturnPath.getPathForSpeed(5)); this._pathPosition = 0; this._state = Zagger.State.SwoopAndReturn; } @@ -170,7 +176,7 @@ export class Zagger extends Enemy { -20 ); const linePath = new LinePath(zaggerStartingPoint, this._homePosition, []); - this._currentPath = linePath.getPath(20); + this._currentPath = linePath.getPathForSteps(20); this._pathPosition = 0; this._state = Zagger.State.Entering; } @@ -198,7 +204,7 @@ export class Zagger extends Enemy { private prepareEntryPath(homePosition: Point): void { const linePath = new LinePath(this._location, homePosition, []); - this._currentPath = linePath.getPath(20); + this._currentPath = linePath.getPathForSteps(20); this._pathPosition = 0; } } From 7ab76428419e883b75150e82b10588deb9552e27 Mon Sep 17 00:00:00 2001 From: "SINISTER\\paul" Date: Wed, 10 Sep 2025 20:55:44 -0400 Subject: [PATCH 16/32] Add spider images and switch to using it. --- images/spider.png | Bin 0 -> 25099 bytes index.html | 1 + src/enemies/Zagger.ts | 8 ++++---- 3 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 images/spider.png diff --git a/images/spider.png b/images/spider.png new file mode 100644 index 0000000000000000000000000000000000000000..1b742d179050735f438468fa3eb72d221da66f52 GIT binary patch literal 25099 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

cAS9>}*qf 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 A>;M1& literal 0 HcmV?d00001 diff --git a/index.html b/index.html index 3e3a564..13ada3d 100644 --- a/index.html +++ b/index.html @@ -82,6 +82,7 @@ { name: 'probe_explosion', url: 'images/probe_explosion.png' }, { name: 'saucer', url: 'images/saucer.png' }, { name: 'saucer_explosion', url: 'images/saucer_explosion.png' }, + { name: 'spider', url: 'images/spider.png' }, { name: 'spinner', url: 'images/spinner.png' }, { name: 'splitter', url: 'images/splitter.png' }, { name: 'splitter_fragment', url: 'images/splitter_fragment.png' }, diff --git a/src/enemies/Zagger.ts b/src/enemies/Zagger.ts index 4be8146..f567327 100644 --- a/src/enemies/Zagger.ts +++ b/src/enemies/Zagger.ts @@ -65,7 +65,7 @@ export class Zagger extends Enemy { } getCollisionMask(actor: Actor): Bounds[] { - return [new Bounds(-20, 20, -20, 20)]; + return [new Bounds(-40, 40, -25, 25)]; } getDamageAgainst(target: Actor): number { @@ -73,7 +73,7 @@ export class Zagger extends Enemy { } get imageDetails(): ImageDetails { - return new ImageDetails('saucer', 4, 80, this._currentFrame); + return new ImageDetails('spider', 3, 95, this._currentFrame); } hitBy(actor: Actor, damage: number): boolean { @@ -101,7 +101,7 @@ export class Zagger extends Enemy { } private advanceCurrentFrame(): void { - this._currentFrame = (this._currentFrame + 1) % 4; + this._currentFrame = (this._currentFrame + 1) % 3; this._scheduler.scheduleOperation( 'advanceCurrentFrame', @@ -118,7 +118,7 @@ export class Zagger extends Enemy { public swoop(): void { if (random(0, 1) === 0) { // Swoop down and off the screen. - const lowestPoint = new Point(Math.floor(random(10, 430)), 660); + const lowestPoint = new Point(Math.floor(random(10, 430)), 670); const turns = random(0, 2); switch (turns) { case 0: From f1804298bbe3a15e93b32f4f8e5da5152b6dd772 Mon Sep 17 00:00:00 2001 From: "SINISTER\\paul" Date: Wed, 10 Sep 2025 20:56:41 -0400 Subject: [PATCH 17/32] Speed things up. --- src/enemies/Zagger.ts | 2 +- src/waves/ZigZagWave.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/enemies/Zagger.ts b/src/enemies/Zagger.ts index f567327..b40bddb 100644 --- a/src/enemies/Zagger.ts +++ b/src/enemies/Zagger.ts @@ -204,7 +204,7 @@ export class Zagger extends Enemy { private prepareEntryPath(homePosition: Point): void { const linePath = new LinePath(this._location, homePosition, []); - this._currentPath = linePath.getPathForSteps(20); + this._currentPath = linePath.getPathForSteps(10); this._pathPosition = 0; } } diff --git a/src/waves/ZigZagWave.ts b/src/waves/ZigZagWave.ts index a51f659..e9c260e 100644 --- a/src/waves/ZigZagWave.ts +++ b/src/waves/ZigZagWave.ts @@ -79,7 +79,7 @@ export class ZigZagWave implements Wave { this._scheduler.scheduleOperation( 'deploy', - 250, + 100, () => { this.deployZagger() } ); } From 10bbe918627471d104cfaea4747092ee33c2cde6 Mon Sep 17 00:00:00 2001 From: "SINISTER\\paul" Date: Wed, 10 Sep 2025 21:34:13 -0400 Subject: [PATCH 18/32] New explosion sprites for spider. --- images/spider_explosion.png | Bin 0 -> 21546 bytes index.html | 1 + src/enemies/Zagger.ts | 6 +++--- 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 images/spider_explosion.png diff --git a/images/spider_explosion.png b/images/spider_explosion.png new file mode 100644 index 0000000000000000000000000000000000000000..caab693a74a727b39c4b4fc411379d373c412587 GIT binary patch literal 21546 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%0A(I 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 literal 0 HcmV?d00001 diff --git a/index.html b/index.html index 13ada3d..4a4d500 100644 --- a/index.html +++ b/index.html @@ -83,6 +83,7 @@ { name: 'saucer', url: 'images/saucer.png' }, { name: 'saucer_explosion', url: 'images/saucer_explosion.png' }, { name: 'spider', url: 'images/spider.png' }, + { name: 'spider_explosion', url: 'images/spider_explosion.png' }, { name: 'spinner', url: 'images/spinner.png' }, { name: 'splitter', url: 'images/splitter.png' }, { name: 'splitter_fragment', url: 'images/splitter_fragment.png' }, diff --git a/src/enemies/Zagger.ts b/src/enemies/Zagger.ts index b40bddb..08aaf57 100644 --- a/src/enemies/Zagger.ts +++ b/src/enemies/Zagger.ts @@ -52,9 +52,9 @@ export class Zagger extends Enemy { get explosionProperties(): ExplosionProperties { return new ExplosionProperties( - 'saucer_explosion', - 4, - 80, + 'spider_explosion', + 5, + 52, 0.8, 'saucer_explosion' ); From eb3d0e0072cac9014040cc2c679bd7aa192ba1df Mon Sep 17 00:00:00 2001 From: "SINISTER\\paul" Date: Thu, 11 Sep 2025 21:25:25 -0400 Subject: [PATCH 19/32] Adding spider explosion sound. --- sounds/spider_explosion.mp3 | Bin 0 -> 49572 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 sounds/spider_explosion.mp3 diff --git a/sounds/spider_explosion.mp3 b/sounds/spider_explosion.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..18f6ffa3f7c6cdaeedafa5972e1089370d7abe0d GIT binary patch literal 49572 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�g{j22?fo#oBiX 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 F{ts*A`Ue02 literal 0 HcmV?d00001 From 3c4734be57435d89868065206918bf751e385740 Mon Sep 17 00:00:00 2001 From: "BLACKBOX\\paul" Date: Fri, 12 Sep 2025 11:00:46 -0400 Subject: [PATCH 20/32] Clean up swoop scheduling code. --- src/waves/ZigZagWave.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/waves/ZigZagWave.ts b/src/waves/ZigZagWave.ts index e9c260e..2967534 100644 --- a/src/waves/ZigZagWave.ts +++ b/src/waves/ZigZagWave.ts @@ -43,10 +43,6 @@ export class ZigZagWave implements Wave { debug('ZigZagWave.tick'); this._scheduler.executeDueOperations(); - - if (this._numberOfEnemiesLeftToDeploy === 0) { - this._scheduler.scheduleOperation('next swoop', 0, () => this.scheduleNextSwoop()); - } } scheduleNextSwoop() : void { @@ -57,7 +53,10 @@ export class ZigZagWave implements Wave { this._scheduler.scheduleOperation( 'next swoop', timeTillSwoop, - () => zagger.swoop() + () => { + this._scheduler.scheduleOperation('next swoop', 0, () => this.scheduleNextSwoop()); + zagger.swoop(); + } ); } } @@ -79,9 +78,12 @@ export class ZigZagWave implements Wave { this._scheduler.scheduleOperation( 'deploy', - 100, + 50, () => { this.deployZagger() } ); } + else { + this._scheduler.scheduleOperation('next swoop', 0, () => this.scheduleNextSwoop()); + } } } From 9de7ea8c0b7823aa22f569e074234732dbcc693a Mon Sep 17 00:00:00 2001 From: "BLACKBOX\\paul" Date: Fri, 12 Sep 2025 14:41:19 -0400 Subject: [PATCH 21/32] Get two swoopers going at once. --- src/waves/ZigZagWave.ts | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/src/waves/ZigZagWave.ts b/src/waves/ZigZagWave.ts index 2967534..15c0494 100644 --- a/src/waves/ZigZagWave.ts +++ b/src/waves/ZigZagWave.ts @@ -15,6 +15,7 @@ export class ZigZagWave implements Wave { private readonly _world: World; private readonly _clock: Clock; private _numberOfEnemiesLeftToDeploy: number = 15; + private readonly _swoopers: Map = new Map(); private readonly _scheduler: Scheduler; @@ -45,20 +46,29 @@ export class ZigZagWave implements Wave { this._scheduler.executeDueOperations(); } - scheduleNextSwoop() : void { - if (this._world.activeEnemies.length > 0) { - const timeTillSwoop = random(1000, 5000); - const waitingZaggers = this._world.activeEnemies.filter(enemy => (enemy as Zagger).state === Zagger.State.Waiting); - const zagger = waitingZaggers[random(waitingZaggers.length-1)] as Zagger; - this._scheduler.scheduleOperation( - 'next swoop', - timeTillSwoop, - () => { - this._scheduler.scheduleOperation('next swoop', 0, () => this.scheduleNextSwoop()); - zagger.swoop(); - } - ); + scheduleNextSwoop(swoopIndex: number) : void { + // If no swooper has been chosen yet, or if the last chosen swooper is dead or waiting, schedule a new swoop. + if (!this._swoopers.has(swoopIndex) || (!this._world.activeEnemies.includes(this._swoopers.get(swoopIndex)!) || this._swoopers.get(swoopIndex)!.state === Zagger.State.Waiting)) { + if (this._world.activeEnemies.length > 0) { + const timeTillSwoop = random(100, 1000); + this._scheduler.scheduleOperation( + `next swoop ${swoopIndex}`, + timeTillSwoop, + () => { + const waitingZaggers = this._world.activeEnemies.filter(enemy => (enemy as Zagger).state === Zagger.State.Waiting); + const zagger = waitingZaggers[random(waitingZaggers.length-1)] as Zagger; + this._swoopers.set(swoopIndex, zagger); + + zagger.swoop(); + + this.scheduleNextSwoop(swoopIndex); + } + ); + } } + + // TODO: This could be done such that we pass a callback to the swoop method, which gets called when the swoop is complete (or die). We wouldn't have to call this repeatedly. + this._scheduler.scheduleOperation(`next swoop ${swoopIndex}`, 0, () => this.scheduleNextSwoop(swoopIndex)); } deployZagger(): void { @@ -83,7 +93,9 @@ export class ZigZagWave implements Wave { ); } else { - this._scheduler.scheduleOperation('next swoop', 0, () => this.scheduleNextSwoop()); + // Get two swoopers going right away, then let them get re-scheduled as they finish. + this._scheduler.scheduleOperation('next swoop 1', 0, () => this.scheduleNextSwoop(0)); + this._scheduler.scheduleOperation('next swoop 2', 500, () => this.scheduleNextSwoop(1)); } } } From 9edf8727783ff32aaab91a61d7f9e99fbe34abed Mon Sep 17 00:00:00 2001 From: "SINISTER\\paul" Date: Mon, 15 Sep 2025 20:37:27 -0400 Subject: [PATCH 22/32] Improvements to zigzag wave. Added web shot. --- images/web.png | Bin 0 -> 42192 bytes index.html | 3 +- src/enemies/Zagger.ts | 54 +++++++++++++++---------- src/shots/Web.ts | 87 ++++++++++++++++++++++++++++++++++++++++ src/waves/ZigZagWave.ts | 23 ++++++++--- 5 files changed, 138 insertions(+), 29 deletions(-) create mode 100644 images/web.png create mode 100644 src/shots/Web.ts diff --git a/images/web.png b/images/web.png new file mode 100644 index 0000000000000000000000000000000000000000..6df0c5dd52c2de850f30cc5b7405f120de1ad54d GIT binary patch literal 42192 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 w1pjBB{#kc_= this._currentPath.length) { - switch (this._state) { case Zagger.State.Entering: this._state = Zagger.State.Waiting; @@ -175,28 +178,34 @@ export class Zagger extends Enemy { Math.floor(random(0, worldDimensions.width-50)), -20 ); + // TODO: We might be able to call prepareEntryPath here instead. const linePath = new LinePath(zaggerStartingPoint, this._homePosition, []); - this._currentPath = linePath.getPathForSteps(20); + this._currentPath = linePath.getPathForSteps(10); this._pathPosition = 0; this._state = Zagger.State.Entering; } ); - this._state = Zagger.State.Waiting; + //this._state = Zagger.State.Waiting; + this._state = Zagger.State.SwoopComplete; break; } - // Possibly at some random chance drop bombs? - return; } + // Drop shots at random times while waiting. + //if (this._state != Zagger.State.Entering && this._location.y < 500 && random(0, 100) === 0) { + if (this._state === Zagger.State.Waiting && random(0, 100) === 0) { + this.dropWeb(); + } + // Follow the current path. switch(this._currentPath[this._pathPosition].action) { case PathAction.Move: this._location = this._currentPath[this._pathPosition].location!; break; case PathAction.Fire: - this.dropBomb(); + this.dropWeb(); break; } this._pathPosition++; @@ -213,6 +222,7 @@ export module Zagger { export enum State { Entering, SwoopAndReturn, + SwoopComplete, Swooping, Waiting } diff --git a/src/shots/Web.ts b/src/shots/Web.ts new file mode 100644 index 0000000..57d9f4f --- /dev/null +++ b/src/shots/Web.ts @@ -0,0 +1,87 @@ +import Debug from "debug"; +const debug = Debug("Blaster:Bomb"); + +import {Actor} from "../Actor"; +import {AudioPlayer} from "../devices/AudioPlayer"; +import {Bounds} from '../Bounds'; +import {Clock} from "../timing/Clock"; +import {Direction} from "../devices/Direction"; +import {HitArbiter} from '../HitArbiter'; +import {HitResult} from '../HitResult'; +import {ImageDetails} from '../ImageDetails'; +import {Point} from '../Point'; +import {Scheduler} from '../timing/Scheduler'; +import {Shot} from './Shot'; +import {World} from "../World"; + +export class Web extends Shot { + private readonly _audioPlayer: AudioPlayer; + private readonly _scheduler: Scheduler; + private _currentFrame: number = 0; + private _firstTick: boolean = true; + + constructor(audioPlayer: AudioPlayer, clock: Clock, world: World, startingPoint: Point) { + super(world, startingPoint); + debug('Web constructor'); + + this._audioPlayer = audioPlayer; + this._scheduler = new Scheduler(clock); + this._currentFrame = 0; + } + + getCollisionMask(actor: Actor): Bounds[] { + return [new Bounds(-5, 5, -5, 5)]; + } + + getDamageAgainst(actor: Actor): number { + return 1; + } + + get imageDetails(): ImageDetails { + return new ImageDetails('web', 10, 100, this._currentFrame); + } + + tick(): void { + debug('Web.tick'); + super.tick(); + + if (this._firstTick) { + this._audioPlayer.play('bomb_drop'); // TODO: Find another sound for this? + this._firstTick = false; + } + + //this._currentFrame = 1;//(this._currentFrame + 1) % 4; // TODO: schedule growing size + this._scheduler.scheduleOperation( + 'incraseSize', + 250, + () => { this._currentFrame = Math.min(this._currentFrame + 1, 9); } + ); + this._scheduler.executeDueOperations(); + + const speed = 10; + for (let step = 0; step < speed; step++) { + this.move(Direction.Down); + + if (this._location.y > this._world.dimensions.height) { + // When the web leaves the world, it becomes inactive. + debug('De-activating web ' + this._id); + this._isActive = false; + } + else { + // Check if this web has collided with any active enemies. + const player = this._world.player; + if (player) { + const hitArbiter = new HitArbiter(this); + //TODO: Do something if the hit is ineffective. + if (hitArbiter.attemptToHit(player) !== HitResult.Miss) { + this._isActive = false; + } + } + } + + if (!this._isActive) { + break; + } + } + } +} diff --git a/src/waves/ZigZagWave.ts b/src/waves/ZigZagWave.ts index 15c0494..54322c0 100644 --- a/src/waves/ZigZagWave.ts +++ b/src/waves/ZigZagWave.ts @@ -56,12 +56,22 @@ export class ZigZagWave implements Wave { timeTillSwoop, () => { const waitingZaggers = this._world.activeEnemies.filter(enemy => (enemy as Zagger).state === Zagger.State.Waiting); - const zagger = waitingZaggers[random(waitingZaggers.length-1)] as Zagger; - this._swoopers.set(swoopIndex, zagger); - - zagger.swoop(); - - this.scheduleNextSwoop(swoopIndex); + if (waitingZaggers.length > 0) { + const zagger = waitingZaggers[random(waitingZaggers.length-1)] as Zagger; + this._swoopers.set(swoopIndex, zagger); + + zagger.swoop(); + + this.scheduleNextSwoop(swoopIndex); + } + else { + // No waiting zaggers, so try again later. + this._scheduler.scheduleOperation( + `next swoop ${swoopIndex}`, + 500, + () => this.scheduleNextSwoop(swoopIndex) + ); + } } ); } @@ -96,6 +106,7 @@ export class ZigZagWave implements Wave { // Get two swoopers going right away, then let them get re-scheduled as they finish. this._scheduler.scheduleOperation('next swoop 1', 0, () => this.scheduleNextSwoop(0)); this._scheduler.scheduleOperation('next swoop 2', 500, () => this.scheduleNextSwoop(1)); + this._scheduler.scheduleOperation('next swoop 3', 1000, () => this.scheduleNextSwoop(2)); } } } From 1ab2bac9e4b1347ebdaa11220616523f3f39e5a8 Mon Sep 17 00:00:00 2001 From: "SINISTER\\paul" Date: Mon, 15 Sep 2025 21:11:24 -0400 Subject: [PATCH 23/32] Unique spider explosion sound. --- index.html | 3 ++- sounds/spider_explosion.mp3 | Bin 49572 -> 15717 bytes src/enemies/Zagger.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 3211984..ff1098c 100644 --- a/index.html +++ b/index.html @@ -168,7 +168,8 @@ { name: 'player_explosion', url: 'sounds/player_explosion.mp3' }, { name: 'player_hit', url: 'sounds/player_hit.mp3' }, { name: 'probe_explosion', url: 'sounds/probe_explosion.mp3' }, - { name: 'saucer_explosion', url: 'sounds/saucer_explosion.mp3' } + { name: 'saucer_explosion', url: 'sounds/saucer_explosion.mp3' }, + { name: 'spider_explosion', url: 'sounds/spider_explosion.mp3' } ]; audioPlayer.preLoadSounds(sounds, onSoundsLoaded, function(errors) { alert('Failed to load sounds: ' + JSON.stringify(errors)); diff --git a/sounds/spider_explosion.mp3 b/sounds/spider_explosion.mp3 index 18f6ffa3f7c6cdaeedafa5972e1089370d7abe0d..396f0e7e130c3f65d2b40a21c505aad737530c45 100644 GIT binary patch literal 15717 zcmeIZ(KxuE>p*WPv zjeGARr7nj)&wAH6|H4`8y*|wBJ+o$wtZRO=XZ9XL9eEJoFCtc!mb%w-CIEmOgAMmo zR7T3(K+4L<{Lk9|o&5RHI`IFg{%_GR!1La9>3TZ=Cjg+10zj_ezXpDd{59&==wD<0 z7sqQnuL-;+>Y9XWGXGL|P4zWTuIagE=$e_otX;Ep&B--a*ZjHW@1Cy@((U>nhBOoaGUV6wKdpf-rnl1Lfts-gyppw&P#Vxw38$oOo9ig?Tb-ZZ z%;dJF%th18t5X+-!JdrAktHbbxB_w)!ovb~5Df9Pc@qG01q7?w>L<6Xo61LkR)|w-_AR`5 z1*)iSonl(vjSwqJNp|?Dj0yZ6&r3$ZS_O%Sm&vPB0us*cZw=g%3-o0s4HEfnD`jsla5r_)wKU3%+NPus?tUWZ$^=I5MUV3qSvo9-e`!}|3z zYTZMURjv65=7}QW+YP2o47%Jsxxqzm;ZlHJbC^XjX^-fNmBT@?=GG!@uoHBGEsC5m3)- zvB*|r_OC3bXtwBwA(#w|3Hs+p`v@dqVhg#?X1o z)Q~GFpa;R-(cs4ad%AS;2XkndzQUm_Xsr7kY!M#-%w8#us}6@6Wr!?t1=cO~7gUk3Ul1M8rLfSJn1zEpWeHyx<9f-WLjUYbkvDsIp0} zz!knG5Gm2?%J=B0ex;pinA7^((_g$=iJuG)A46Mz8F*^4)ujwW=4Yo+BwuQRRoYmU zWzTAixU;utms-&a1qxTv>8`d43fIWf`Nhko#t<_-%$}g})vEmu_I3VTSrG|QB=FFM zn%|OX&_CwH;$xKgCILR}+VJ z&=Gpx&v4>OVQSiQk}%t417E9sI8TXN@jkV*Nlt%x-UWm8U6vMoOZKdBzfktiI<9ue zme7*B`h8+GBN5sU5{yKmn!jlamW(%j;yoreuC&Rq%v(@e=s#wMuime8nDS5yDZ$f< z*;uw$WHm*PwEfrmOO#-5Q) zCC!&+@sRZTr@!OxK%<+|qLW&4trHg?|CJi`*KB0&nULT=7ZLE)fgfOsD&kYN_kEFRhPfq)HN8H3%ln<|$n?)KH0bw-uPYKa% zU65KGPYYA~Iz2MPx2V6{#$C-fSa5`MI3$F{%9nQ?$LBvmV58&e z43()0zMr==o2BGrVH{+#TshTkT|ZV3PhN;6E?Mw-Lww$D8%Q?GB!4fin_M3@=cs5A zlbx$Bl%`>ld&QE{Ih5gn4UECD$4w!bGFBXfLUl^hX(5U&&K1flh7@#JRUe>=AZT}a z6UEjKt=LjS9iQyhPVe&LP~V5db*w0yn7mOZg7iPwaiID7tsykw8HekYnx1IK*C}*s z$^6Km^J9re4T~GiP}KrWTV1%Q+j1BGnknBF!+G=QX za>jn9L-MXtF)o z6@5qa&q_8h*&_wIrT*|8=(akO8Ut&PL0Nj3%$BEfnU12`EDaN6XS977Mom`6&pD2eFIwTb7-PCiS?SC!?0N5X=SGJR6@y||UWi3FPpfynQ zOI0A-7e8sg!(WqoL54atKmFx>-`_Z>vSxgKPu>FOB~@6^1&@4d#oXXK5soJ~)^5DD zqw!VN6D~Yz49do-?dwYnm*mGuCZ+}((#jWFjVPlN+PMxYCqBE%q;r7|mG!rxEFsAr zK4Swtm;nR%0xB8{QBcLCD#YkW9&>an2cwrlkqa_r6t*x=tq*F@QP@^k(RURM=QO-h zS=NJ&>0=diOLoOyH!nXAcds?3(o2bwkFEDZE~CeV&Cr-O*C2JqJ~?UD0#QRMWddxE z#s(fLHK}81cTZ(Gz1}heeqo~|;FVGXH_wY3k)$%W2vED7AJ27jyHH}1CDO1|Izy*@ zkM@>AaV$lVO0r`>iEo!G*-!WE1ZDZy@7_}!}(e?ZC6>dXOgtCN1WIT za=H2JZ5(YZA98oKWCG3CiQa^g2w~QoJi76!%55RxgI)VfX#yYLI6Fuwn=j2yip|JV zizTaSc<<;nK2W6SiBMkP={cLJC5a{Z05}v(k>J@6Wn0J&8URvnGwm}^-5Bv|XH$7{!OGMRL`1e<--MB+ zeu{(G$h{LkSu}8 z?PY|$CMNOk(FP4%@if+*1ThbN-M*q$V?EAUel15IE`po8kv&zdFp~HLdG=0%B`asI z=OuJKSKI^SUitH>;-*4VmPc<0^HdXEaaWYWk6X)PEMFV7W_Qjv0v$|9eFj?=rx!O$ z9NEy9>oN5fW>$XfrGkxq3TwJw$F!H3GPGlth9j9tYW!kv0Hl_mk6NdEtAhL zTk-VV3*+K`wP!5 z>PxX!u2If6gTztxfkR#w{z|jW`2#VSo6!4nT4`g~ea%#tF)>M<+DA94s#xe{=2ACv z8kxvMjXLr!Vj-D|xhE&pC5JcYbR1;A=VHy2bH2Kds`!usdQ@!-luH~|8X)~PYzOQ& zDD8Wj4)EP2(zCUQBWjD9^|}22q~D3AavlZ|$E{dN%D_^5-lHqw$AzNkh57P~7WPv} zgT5(duR6#oF?VFQhxg3r`5HQbUORRnFv^Xda_GB{wi2em+h>)a&ok@otIU8Ap4;V} zt=^5}na8+qkDBh8PYbA^pSAMnB-pN($t@sFuW|s)8Zi?ImP&n94MGffVdn2dG?U)@ zuG+-PtOkDy_OVVU{@MHV$+q{cR@K&u1=3g78O0pLr71M1bPDF(?;xkx<($V7|H0~l z0x^`mkF^;TKeMqcr#p%<>=>-4NpAaV8lWjOnZNkzfwNh{#8G%7FlCCud+FEVtI1I< zd92*AbduiKw|6qTB-vMCA|pd@LzPJb1~S`PU8j9gBLK%653}LHIL7LxW9~@+1keosh!)u%RoCr`YEu@wS#xp})8m zb0CTSM}5wJ)IMcZ_-4l!JdH!!fhA9*HzkO3hLApDznsvqJNz=0Qs$e3s#RSmyK1Wv z_eotd)lUze*x=i9^+CKooj=sVECF}`s%j9t$NFZ=48iI7g8Q7wO(2ew^{@u~eHyKc zyw?p9;xYe`v*OGVY)u_sF6M>!s~k6A?=zEGI`(L!Tl1JDT`kZ{gHxl;Y-KrV!~cbW zs0_7AvVHsAvg#P4kMM)1g-hdPQ$3vZc7lQ;)C~#=$+_Xdg8`(7;}?ot0p!bQ1{kir zg|LV!wj3)SZ!%nlQ5r9aT{u-j62y%mZx+t_Z8xC{qx%p8nPQ$-p^|$_PgKC3P$!vu zwn0Na{DD1&mHmrvmf2#6=z`adcmxv9ei?=-Fj6Gqqdh|^Cw|yS!0Q}^vySt0yY?Jh zr6zHw(&K8)Xs&InNugqRqan4@)omA4#q3!Ly2!a_moGs{xvHtVr$cKk)?^>Y)03riO-Z;7+Phk-9UW!$^+R}e{ z#k^1UhR3fu{4;G3T+s)FV7g;w%x6V1sPuxXf<8rjsJN7~<1$Gg*Id@57IeM+t0cqUZDGL zD>cjkzpKuL(%$5blFwUbvTj6lQJVpjK&p7n1TDSkn;LHqaa6E19d_ti1tq8#e&+#V zlV%$$C%(S)CH3^DW^>TVT^5=DRc_ye#UvejFp@VYzEIcx4F$leNG7@#nsVLcepCSa zDd6%wcTqs311w_r7`byPmBMd7skFeYiS+6c( zGrfg~CC+pGntYF$|Lg+-ZSt?U_M4qFRA)a)*%08?G~l0^QRQakI+Z)LT6%f$q{&mm z3tan>mZ8e_Q*?*Wn95N@Fj_cgKYsFj#J{JUK4nG`&N~NsUp5+oTbfwg%sai}yty+*ReDZ2 z^?R=rxXzmFY_apY@M zI&0*R;LV|+{&N=k*t&!{VEhV|t&kZS+2bfe@n?fW!{dw5;~kAs}BelpN+Nm|0z8qAp>6G>H>uymDXZDYGtV*S zx8&^VgvjSqx3_+xngub}m)$0R(qoKjkKa4+);|0G&Onz%@>`A9fk;)HErc08Jq4eO zKbXTkquZ>~TP7sf0z`{^nxFk*G?)mAN9c;YURFw+0=~398Ooz|f84IA@nM5I)c~n}gwFhC#ZkYA*?agSLHz!TG@2k>n zUZtXns;orbPx#EMCx0uEq5KBZt)KGQ&9xI=3|N%b$vkafYA5w(){u2REO%X8 zY`$Gx#5~rSL-&%SKCeTgkDG>aB;NP%M1*2uJX9|m$^celtlk}MsCa}zaAk}#xDwb| zH^U0(?;g+uxxng_U$&?$)38PM-|M%c@x01UKCd#GELKVMu&8)a@Vxv;$)R!S*ub+e zTw3z!^Fx~Ux28n6Z#&tPsCbAtLj+Na>gX&>iLIVZE-PHj+^|z`U(Vg7acz~`a=b}1 z#G_!O3f_IaAI8lh(G= z{|s0w{oGk~6yQD*lkse!zqD}TN8*sS8m%BzobLT`goB{!?eL&YkLz=u~$OgnxdJX4MoVt}BrAN!Zze zYYo2e$=)_pgYAiTxDFKWGt0w$+j0G8&EceOU^9wbC_C#t>-HHs*o4`fai>a_O;&ev z3pVYS;oj)%f}){0G3=k;wLw)80@uNjann0sP3YM}3EL+&Oi5vNi*G!#=b3CG_~;po z@TMbg4qyri;5Jd=ASZV;S!<`dL7}T%5TIXR}#8z%I8{5dag zW(ERvY!0=7AlBTu^dXc6K;x{QqwtGt=c_(!bvoOM5tY6j|3Ef#S?;|1@T6&pVQ4F#<7-J9Nwt z9t#)BFtDcq`Y;@bdZ8h2Sm5j-#4#DcGDOv3B1aCF%KPO+_SdGM!xLCsE}zGXWA+T~ z%#jBYD?%!1wnT~r$6|hw#r;G@dKh-2REKA_&S;32R4UnC=BHjI_hBKNe`W&c!~9wM z-_i*!<_S{*T8;Js#+@rR0^@GDb*!HFtp{(x(Y0dBA~|NX_%{3o*z$uDjr_xR2c8*P_S7<-5s&a{m%0!s5xw0VVnkUB_q#7`Y z&BZ-(qyC5eUK2oVozv>s%~!yC$gRxyKBg6wn!m5LEdS~uZKVD>%V%d z2G=Y?Qzwwgg3dI;D3nvy^BCNB>tF|QRmy@F2`j?D%O;iUI?5ad%N(xljTCv3R?^fABQAA17JPa z4y#=gqDlV{6a7D*f_VX(NEVYcpZNH1n%61U7T7cKpCvQJiG>8q_~nksC(6}q{V1k- z^1-&gi%9skvd~bXSmyB7cfe(1`7JF(HZg@*QBe%@>D(;LFW<4Nq|EUb9i@5a zfkV$eML)d5Sz3zFN!QBZ$xec{Jzu9E6r^HfJg)G9k(Vm5n`O+?F44xzy2RKND%3kx zV&RnMBjtfQsFckQD#eAB&BC)mL1v_qDb;GGXY66q@fF!=MKSd$wkr0pRA(`1iA>&R zye%ry=iKF?YGpt-;_Vc4yw=O`RmOX7l7*^*T&0tUXX@hHdX;Wh513ZfmFSx?Ck0V9 z7l*w!CE}vxFQZb^UEtL4)Tb{j)!K`LS)^CEZeqS>5jT9QiO!E+Jg7<7{e4;c%5)}! zPnx7!ortQ1Lgbg+hUME${t2C&;an^Cc5$nH83orgr*j*XZe3kbSOI!fwwnW;1TsHpc0u+V&1p zvGMD4zJDdtNtCvTthr=?hNx*tJ@epn^4xP5DQZ#E@y5@ftCAR&8c(7GkkY%{quD;Ttq!y5njH79X# zm!?v*sh2kbUcL0z3M$wwQd#e45;Gak;p67@%I;yVzYqglhRyp0x8+-p1a~VNc?O zW+P~f3xagYj$jkkY8{#YPWdcwNiOj4%K}ib*b>p38><&sva3a7F57_u=277$t?j@? z9bMhb2~$-SZ%VeogTg@4!yro`gf?UWpjN?pfQe%h_K9U@V0YIOvn;5s5(Z3i=EWp0TKGHt1X#mz)8p4OS4@WtfA?|Cx;R?E9nIf z6ba6wA~s8oR8UoF-duJ9Y>fS^OQ8Q`QPD3>?2Tn?0oSS-pL2&J>EG z21B$s=o!eV2AkT<-GZD5oEDP9NTvco=3H-rmP{a_+yTOMM|WG_eb^f(XeWPIu4@LF z-8Udq__wzDs$ufqXZ=1MDW?ra)BG7@BF0)x&G8=pd9Eq_fiBaiL zeazyHO1S_T)QgKkOctH!cJN7(p!nAGbc}g7%4NG(_T)4jyuAYo(SB|!Kh}`m^!^a{2 zH`pe`CWH-005}EsC}Q-{ql<)TkV9B74wGf_PWP@qhU{Zb#_0a^FtE?ddPtEqZJx5z zghtz5M>z7bnIU6jiS50JP(>cNsAPXtQmYpTnWDT6Dpta6w8WBGX=_!DgISS{<5}66 zDM@(dzsj}8{5!{_&mb!wzo2=zl?OY-QtvFtdDD7fG#DSaEiBlvNF^+_niy+R-z@@~ zr_lDt78Vwip42;Oq9?N180RA`e?as zp_lHlU1RAyLQK_Ww)hTruL1cbF55!OAr$RHmVp^h&LImjVA4X0U7Y9)8MJx?_J+tJ zSN0kQoyD0rsmkRbMW{67W8X3bc9TtHG76?cT!(I?)r*i`Qlkr0GP1uH7~bU1Tda8# ztgd40BBWE!M7z2Sjy0t~V4WmGtJ0UUwGI3$SOV9So%k_h+0Y_G%Sw85A#xu5iPsDF zdek}3aVE0-NG_uG=0}wp`zNP?vIQV&^2 z+vN%^?&Ku1CU5kdn4^Z#jTY3}yAbV(Ne?-Q$7=zSloClf{foj@usThpMrD+L<-$^m zkEMRwZl%q58jkZn!=y&2BJX`!u~(mi*Bc!8^R$atEkCO&HqF|4P^|X$+{;^D66OqH zuH!s_g3_Aw>}RwMTEr4_(oY%pAqoqTV5MQaD1fYE zjWGIwUPRl_GkD6C`qPkZ&vD|sgT>_9L2Ozg3@ebtb$oW@9qxl5e3H^JWNxrZx;WAZ z?!tIyNl;xt-oEKxzR);VxlOaQKIO0Nu5NC7lRlSaEgy%C+I zqS#1{o39qPlNdV=&+a+a@n+R*f|gPhoIw{e`g^F3N4#UGmv50uZA_EDv;BA z%j|t&-wnAV7%A8V zt_Mwt*~_%HamP5CXc&$pmNXpxv){tCi^JRO4Kpm_yjs| zb!3s(ZHl1m7D+oX`u&6yCZ6%#Docr?H3$Y%J`@oic_t}H+8cm5lkRFQI3 z1@S`u;}4203{AGPgKr;g_q=Z6i36twMG8Zj!atoma(t#-gYJZk99!3gNlLR3LTDl?h z7EHNzq`Hz~iDkG?kOuE7BJC>Z-1~}A`l+?3h$S?{`U8m(0C;m>{L$U(=8?Ciqq(Gi z4MvSCD*FtneO2O^6wG}ieK<6(Gh?eKAQ9302ZlIH5W@R8bWI*F2Xeqz;+a3GZ_sSh zpR|){zj}kb2xu1*%I|#0a`1^-$L>U|NycM1+2U%?IR=2xhxl8?1IwdcBRZ5Vt)+3~9`_Xf=O4xT@Er!q>Y)Ye5 zU6Jv{*f)UL6Vnx7Zf`o_AC3vC$i>~@){0f4L|5neSc+Xz4JVIUP=&Tv685NAj21zI zc{!(v14fN%0z`fo(FZjMONg3pf2|%QibxO~#a;>X(=zW$={*2uaW*588(vo3&<~jD zkj0E)IB4ZIkHAli+Db3$*bIh-9`KSrEIcxWQSS)0a$JBy%l!PM?Fe42U-UTKj~h;S zWHTb$iofww7EZPL)-NTVf5r@1WJlrD88p?M@<4_Ex##N98du7N3@Sq<#RfrAwbNrt z9$0@Zd>!gLyd+bM+&wtL~=tcG-|cgXDvr-@O5yB?1SIY zuhwXtb{El7F2jEoPM)Xp_K-558*jQ@X~M&}R<%v4F+E(A!b|(?BdoDOGH+iExkm8S zQ*?YH%wR-HQY^l8ML|B;mh&@PCSA%MrJYHoShxmrGBHtz-~fV@4C?$EuK$K8ng}X^ zsAcKMGb(neFgSV&ia1Gx3TY~?qBME%fy`oM;;PTnBxMyXX3`c^&O=sr)Vnp!#@llz zrQkx$BSrSHGSj6pa-!Cf9dyyTBa~&WJ6bLlnf7&G{Z**8TyMlb1HSSEv#F#tSAE_( zG~u68*ry?N4rbF`WLnCyBJY#%$U63oEp9KCp{(fNa_TexO>^s<-kL0`I8_l4&Zm*! zH9@caRqhknfd!Ki!`{|No#ug1vzqV3KII#s13B5Lr0o!DSHxQeG7u#_>wR>czG$=%LUO@x~0ti zzR5&Bamq6%Su{cRX4F|OZwo1XR?=UL(R#*B@w&qy=nbn%)s*#3E3Xs9l^6h7ERGXj zd6ZW3W)Z;0??X>+8z-C$c;CrkKSEAM_D?D`WiIva*h5DB?!bgPIgqDSVd+1 z=SC@McgnoLn*Q&#XP53OO^n=N3pBi_8Pu!xQZXvtK6EdipJ~`>oc^S27Joz=>M*y= z#bB0yeq<*KE)jc#oOMsHgxQL6_M>;U z4T=FtO^!3)+n(zVjy-*lt@p%T)NLi=N zRa+-d0N^8WE<7q!cv?PnVwfU%zGpx?4+p#+{3*XD5fF~oJk*RM`ta_3)IY0%1EY=g z52viNxBPt+69ziDWD_R-++fe!LZq{i9g5?L=iL`HkIm``)#*1&Y>*3}vkh}i@d`1z z_{(oMpPROo6L=S>B%3dHXyV?mR(Pi!j4t?*FzMa2T~&RGW9IF9vzJE( z+jV`>pZ(gqD*IoKm<~$VRm_s<1#95j!-T{zdczYxmCKY0K6=-r75m@2b;rE9Ti-#a zX42F)bgt?iR;saMq*aI=^=hi}KQXg{>ej0wZ@;^D&$fo`1ruoM$9C&5<0+FiRGi*H zW>>MyF#u(99ZGFA54Xb)JP}J@CwnI9*@ZWY^>YO0tEj)`=h_gUWbFqp z^%GB)o2j$goG=9E>kFYRLyvE>ieQ>8IaU%oU=b%+eWXdXH3RP}hk3o=wwB{AjrR>w zX?P=9@3`D%y@+6CXF6idFq z>;AaAN+yY`tdS4#|3GLiTNBlPzewCaw73LM)3$RkxUNgKB{=i-9 zuZ)~>U^82#k{u+2=9ai)-$bir?F3^`u6u?c9(L5tJzap!CysAyBUqP@2@-ae?5LE0 zA8nW)(_9v1ejne3hnmohYQYk;g5b>xh1BFa-maR3MVuGV&NNr$JjO9iE=V(Jch|%| z)L0D6mh5rPg?+d|4qc1~0e6x1xeFhb$fgla2S{})xKZn$uH_dg<*$wb&)R3C$e zvM!mil|h*Ei4mPjJLi0IK7Zyh@oRA8icEh`r>$WVA_f8gNN;XwnXw9Xp|-}3Shy+s z{N!0}FF(i~JzGrr5T1{kbs=QAz9C<{yyY<{1e##(r(Xa^(hDikS3aWD{2D3?q5*MM z`O}AB67qMUG1;*pdTAm3vDuc?x!k~;K7~}3{XYECKohwCb3u7eq6d(1 z0A_$Prk`3bf?jxdP5=Vn6@LU0F38Asr>y+r4?et0B-fI$k~5`d_(8->{R{jZy z68Re1dn8cFKvB;g6C;!;j^%H+hl9Sx5yGlxK0Y@OU|qKOmS~$xs?PwiEoiiQ1D4qEjGO_F=BjM3v1#raABy+9YsIcZfOMm~@hTfi+K{pi`w_euX_!L12kwyQ9+_fl?V{f->r$>9ra7~PQAImBxXXS#iG3sM*6e?V={^6NVJR$b}pLV3Pp>o zrMI1i7%lbu~@;edr$N%64R+CU*oxkd@3 zdOi2C|6I!0=Z(CREaR6(sVMct)8?iX8|B{APwzrc%)6Ks?)v6r`_{p~e>TnS4mF6} z$xX6~j21Vf*aS;U5sB}|t0tvp5T&HRh{&YCSHd33_V z6IfcK6-v~siXZjX&uCW0ZmW({^A9(!3h1QJn*j#^vPYkCG<i1Q?TZMWQvs? z0mC+K`(7{K01*dDIDJvJpJgAKcb1vI@ab{pk|s5R(IV|l0ZA(3T|ts9iB~6XuPL(V zA>aRe34RCIbY()Nt7lTkXu-1j)xWMK$V~}QVCgtUnqP|3lY~gcVuYlBUhPp~!6HsEngg++Drs@&<+P#^8-oGno}? zCp{iyMh{i;NH8J6ilaCUwCN%iy(5dw;L;(_rg$Q$dc&P2V0YOzeTG@#6%gV!a~K@p zn)p=JEyV>8ByWwWiF-fakJeI=hzr+xH{#+~U9Ce*^Xh2PX*PI5=o@Mg$Nw?^20YzXn@PnZr#%MQZP>2k4F0l{Ot6-gDNJ#&O%ti)Y10Aen-K(R-QfH?5L=vZqjw&O_gFv#rW?VNts)RoET^ zMl?IH-L`U@`V(n|{ug!0eRHT`S_pF*i(Y`eXoK%t>KKX`TH3{|Fc`h4AOlBi?7b>) zxo2#EmNN2ypw0Tg(VU(=J~3wO;mX1~4Zn?Z#6#k+796Tf3@UK{m*nFF|{coef5&L*qHxA`1|= zo4Jc$%qJZ^m0^=|MI3oQJ*tVgMJKYQB|+MqP#FgGGIgTtsbd}3p+~??=cGwjF>3Mz zYV+2=DYqN5{e(&3TDb&M%{}bQO%z>neJO14`#q-Bh{ThHAhw0?P7GU@^h1&&tG!}} zxqcSVyt0Y;-xAcQUcvEZLR~6tL|gu%g-_h_ny1vkyI=j%P4f!V& zsU1;m#+R_t`meyL;oc+5TB{Jp$Ne56a0!I}^^h4ASz9iL&Dbn2!{mQ}>8V0&hj03O zOU!Pz^4_J;D}8koqL6P3$d*ep)9AB_WJPu@mp^fwIb=auvI$45C zC&1&<-Q$>7WPgy0kME~Y+up_rF}q67Ri1|8*KLUiA%FhPw^Y=}%dqH%+d_@d?G66V z9=X1{w#jN{yGN@rw>xSP>Ey9r`2}W^#S>g5JU$<>VM@zG5b?C&K;kV8wt)k!q z&unh}XY~ad9=c!-d0?)&C72Q+NI=`yKZv-f6WJ0X0nz#?wJ^(l`jPCa`>&=Nn3(OX z%*2>>hlH%p)q2hrO&3w$qZw|~+-2PN(9g^j(Lj?HaPk>C^Se_ym29~{!!QR!Dox3` zBpFK$suzW%6paLp`T9{4re+L;@*QvhxveDI8RGbHci94kO)qg79e2OXJhgG&)ZLZ} zN6f?x3;@%ZD_t0&*kxP~Mz1cq3;>T)oZ6YULn^fNv=Smu5D`6oc}BJryc#%g*eJT?kE3-mV3QBA#{Fu zZ!o*vGUSAa^my9L`I?t=#NOo7GfEoznOkO`x~rE@Urs0Nd3{41s@$?1wnD}^n^{BEwWwbXJ(#roMdGL8Xnq!QoGE@@c*yLNKJwOa|{4G^iI zyzYaCu=D5xW0%;|G2M!&Fu@t<5^f2z?sB93aBe7sB5@{#UdDxbs3M-5a_OCUsonO0 zqK`}A0dX>iF7z{737Lb{;PGd`+hTW#>D=SUwruhRS znK&BqyrH33;epAis&q}6cd9WV`qVx3FX<(q>J^>4GmJ8**h>#|ONXd! z=;!jDa-MO(Q%H)|U71mvuw!uS5ulvBK#Md%TjmyBEsKg2#532ietpJ=S&O{~oin{DXU zo;tsHz;1OkZ%SdG*nSdNkh@S(XXO1U z6P?_`plyX%@eR{frR7enxAXfzakAF6>gV{vrpeBA>+-M(~Ev>sZ z8e50eTXo#u_Wiy-*Y*7uKG*d*Kb-M;UFSOIyk3v%oby~(FikqZpCpZTbTt26asU8V zY<%JZwe?gqbX3&U)c)t~|7QPv?|%0GYyNN5>So}r-?iWU07w8}>^6V_$jZsd%_k@- zDj_W^tDvl=si~uHWMl%fL?CQYXtcANr;m?+P-y7Q$e7!=@7znqVl#8{^Krz|ii+yG z#>STRuHN4M!N-rEj!#Zc&%Rn*Tv=QH=bx>e{r#iQr(eH*`|;nOk$+Fp@Ao7%b^bK| zPm-b2{om@?ZF{5Yvj6k=|CRrjeu3Y_kpTdSbVe)Qj3l~R^P|TaUjP6;06^(2%ahPm zI-0^MUqB|Y(8%!S^;`dXRehip2fza{OV&^2sp`6&@Ua#+^#SZA%z7 zZnn>#8F4tzJ!`WgM`t{xYEB`Cq%f*Mo3=!S+uXiIx0uZ+u zxE5z-BanSy-33rNMw3$ zWHzLM*jOZZTN*xToiH_*Mhb8Bch5q%YFI%WTBHlnA{$|TF7xft^blQ zY-kFK+!orqU(w>wS#~TXrscy|LW`Ws78|$-jg-;zEjbsQ_iHFz(VMHpa8gOs0ME@v z&J#@ahwbTR4Ic9qfoDnBndr(UA)-UD7(y!qJ2zdgGPgcYhVW=ta6eBR01^heuD!98 zod4;z8dZwWG~NGk9Vt_K0fLb-yOqdmh8Ks9c>$1E{VBIf6DyE*j&Cv$aAj}IKaGaZ zqqwl9jF6znTm08id`X(Flo6xp#)*uYz1lSva;l^mvPWLLlujVxq4<8_GA+IFkOppi z%cYDrUyX02QbV-KVXTG)tFMJgzyKo(}sIQ^|ox0WDmCKK8PH9Xx2`yZz`>_(itH;d_&XQV_!l?@1DX| zWV5vA$wr5Xv5596bWD|o{8CmlYLHOtj(pUt_QOL+u2)bb(bPWw%TYJ&YR18P!_`W8z+!j9ForM z>*seC_wHGE;2UVR{w{4vbmhFZSv=W?SLbtas(tb|Yv7`UV0?ChrL_|J$Y7(^JJsGI z+n-oF)J{%uQm+nJ7>giYb3r!!4{|(7)KC_eynv0_A=9>nPD4%=r5MV^c~3TeP`q>6 zaSApQD%3gptfum)&ENOrb1Qobu!#K6)kd%q$Ko1N&O`3J;-~ABzLiFhMTZ=*qknURDI803_?T?QX4J2a~0j` z3UjBq15GVy-4cI~?3(i^JTwhB0B7~QP-;BeHbh2?cI=@2)m}GjfERhrtFh{Fqe}#*W z9eqGy(y{iDy=%w1NRCqzwdy@6h+CdsF`M02z=a9kaw@Xy0e+jlSoIykY>n=c)*@muQ+JN{2N0t5?B_wW7(BKC0&AO^=5VM@sBAf-@fl4T}KVt>x5P z-E|s~`*thlj+jz@%&n{T5Jz~4vS5fb;c0J!^+%7TwnwJ34ZR}B62$lv$wH;KSmL0! zO~3QNoJ}Md561m_?TO|?mH%oBO5$}x9?C+V6eDe%2b%?#j%T26 zJDlV^?GH0B3VP3OSvbo(Hc*$1gng%9TjMMbMkt8MYrsS|zy(nvR&vhrge{^~CKWQt zcTd57Nn{)*nlYmnlV3H1;V~SO5GmvmVUpnya8yrROvB31hA3v5xunaz*b>O7U$_bX zkW~&@R8o3H^dHmH#{mPJ`2Xy3LPk_Ud<0#$a(9dNfD;ZP2x&~ry-8sr0g zo(u%EtXF`!}mX^)&;UuieG^ zD%x~3dMq*qNv%9tweLeJPvzXWZbAql&lWp%=lCA?T@_EqHQ^>E$A`0H!z)~u4%6Hq zErVkV3HPz4##ikwn3h4dZ@*8fU1AmI=Zcgzo%z8+S;X%w>aVdXuGZh9(xzKnl+&#~ zoxO{nt@h}ZWXiO|?W$98`aCVEdFbwANI&+#6iw0OE5A}NCq?SuLcP9WK(6!&5=*@f+B(j2nqiG+>Jp|a%U4&O=#-8}I^?hISnV;ke zxbsCUKi*nLn-65}s6O9ikpBlcQNWG`2Zl}k3-)O2099$2G~kyxhymCV0gYydIMfza_YNm^q6KrB0quaZfCxKd+pWFY#tbZojscwOlOk(59d&dotrN-E;q{Q>T(!r zk>0oP1-e_Yyn3LKmu1hFgqT)aXbIh9a)2x=aHj=Vb4EpEgAwP5a>1?jRlg}XiLG@d z4dT<?A*#zPS-`_D*99hYPvqlldP- zzPR{?mcx_hXR#&zTDg&5!j1KK;oC2W7_P?vfL0wb9iHlEu*i#)mbVIdn7iFLq_|)D z$T{c1ot9S;@`3MSXf4zKAjb=!e*J^oDS5*50M$5}G%hH?!Cyk8&PRyM_^YuJ3I(Bw zd>JNi>#Rucmds+d=iEyt1BH0r7u%-G1Xw&HADfs6lMmTULq5iL4GghQf*6WU*PAs! zZ&-HHPEp7XdZ4jEd$ySzO9_`)4F+?cNd(n}^On>@&bXqUamkwwBC4fb7CywICc%^$ zPeL=OBOQPFz&zfHUv|q>|8YRbVtPKSd4Hn9URt|PT|tBVCll#48yCxkk$xaQYY#%d zK$AC?COiOyTvJ-E3`4f(qn^dKaW`HZTJgg_LDB<&tKUTM3NI=g;=Q(!dB!d!%Eul9 zgurp{y`IRbtcJ&0^2<5TL8mbA7DZbvxi}L5QFfV1kzCz?N%44T*Oq^td0)_U7<->? zD`GkkC?ud(BhThX_$hoCT(_PHi?{U8G1t58+%t!?gB^S;&V?3E5rt}dst;SzaRbA! zf<~g5q2GOlx~R&SRu7Wei+V`*MXy?kuXno5m$xdG!k}&a?w@vMP3m!W}S> zv)DDJ^^OLDWzn(Gwifq3>fbjq^sbrbY9VBGCt0hOq{83p3i9PPLL-KoUh3r37d~m@ zV!`jsbZRZolhf)dcT;GMN0{S=>BhCbUaduKhluy7&RLbDy zq`_$c9s8=R&5LQ@jBZ*<9zObxs(C(~*CSK~r8qWleaTFm{sUs9$n+y5Loi=3Dsd^jIEw}>KWb^Rsn_V{q-MG4AS=IajbrE^Zsdv;5!B%1o zil|y12Koq@0p-`>q0g z0sj*Vg73+~Y`zYKf=^a$Tx{;eN&Hy0?3q^c0RSMZ!Jutp69bKI(OEFlITOv-NO4AW zWvBMto@|V2gFDBzBw;?sQatVGD<662$iER-IG~6MhUQhaWac^uwPIgn|9TU?kW*K? zxr|Xv@qVqkuldz?Ii+NWUMfi&^4c7VGa(mw`wN?Y>j%99vRqr`$y|zmbI)Cvs1jHP z^RVirp2tM?Ry^1A*{iY$kh<5h+#w7IP`a_{vSTa_HHfd5^0qS-|MI8rt=PKN7=Dtli;nt%UARXyo61QH1`a#k{;P zC~;cF^(tmYTPC!Tc;NS!nZS=y54I~3KMgCEn~oz5U(%s&5m~i}Gl8e)HW8le7Wipw z!>@9UWxfHz-5D49@cdZQ_gyxR`B`B&fB><1_@$Ib{$Coc5a@xaFQKwtj1%kYv1?c* znaQAvjeRlv_78GAfNg!YKgi_{LKS`^2RC)AM<8Y%=RT$TON_g5L2jHIK&%4i3$;%| z3N%jaazZ838ja_?hpHQ+VqsAeP{-DD-_A2M0+(kW-Ph(=@a|2w7qVf0r&DV+|LoGG zMvAj~RXuokEz*Hn=PB3QWT*zU}0vjVGQdwAGYp(F_FMQYKG~Q;MdtAsC+iCi<{bxA2?U$HXU>yZ0Uv=YJh2vmK z$F)4#ubDO_UyI<|sx6PYAd?AoPm1N|s6ZW*frkdEfO~2`L6%juzMb9YCb$3x(eYP+ zAoiDvrlR`|tI}5oa`agCwK5mq08D25lO)!lYr2$XQhi${1+<}?t+~M&s$N|u|86Ox zh)X}l&iZtU5OJDP>se)8x8+weSn9cy?aaX7@XHo&^?E@AC7+?;xup%%b7YT@;@52) zp$34LSXY_&%{Tdm^M~^zA9UkWyC5yS=~awU{oEGPMj{=kCMHCn{_wb~g_8pMLwU>O zIRZjgXMu6L!U8uquEK%m+Iar7h z5l7u+O)QGcl!oDP9JKoZlt3A7U#zfDahTyJ55Z=7b8j?-sf>DZr9HASEO#06|d>S}ZK0O1u!DoQ|V#HED> zeq{$*_s(pkfw}@SbuPDxHNAC>uHD_^X_9!rA0kEVu|ABNBrTzpa%p8ncl#d(| zX}6WCdJ7{b(|DMSW7m?Lozv547~EeqaD{ zE&!(Pm60e~=CT}t`$dgEumvHNmDiuT!b!pB zAO0XGoU{|lhRhxPNFFkUEaZ*2u8hWoOCq-NyzW2fm1Zdq?sO6gTv^FpHmu6acMQrv3}(!d!A zG_q5H*9t^Ox^;wt1IYl!%Z^n6CX~84wEVC~`MQONs|HBKf71V-6(Aof3;M&8f{*!x zV0E}_7it=0+d5D1jd|eQR>CN4HPttpC-KU~^?kFoAB~z>zS;7D%0Wd5y+)5OSM@PZ zo=))_hT^&LZJtbh^Y!9^LimhU$VKc(O1&)g-0eX?l%K?|Z*GG@ywYz#6cZ4XfX@v|Q=)%mKRjZrh@lR+Z2C?pUoaaZX zeEuLOn6x4L`(O=8wPF1&KIC@GZ-VQz=y#Wz?7N@LJTC!|+N5d5Y-hWVmit`firZSw z{2T5l@fB6dp&wBDVxR?rErH{>$FF7iXx<6zT?Heg%J(zoW*;!p_1XsH;@CJkr+-o-fT) zw)c@)Y;{%SC4k)`v2St|K{+4|W=6|ThPq}^;gU9up-{wlXP18fS5LCrqud)R1>kF3 zi@b<{3}ivLTw`7Rmb&_1WPny!5<`G=m)^$Xt8~e5^Nung_0t0u#Kt|9LRy}dS1;*S znyJn-LyF9YT)=?3sj`B-oVk3<=ex4x3l>@g%1h(jrQJ)1s(Kyv0!|Q~RRu1C%SF5@ znnl)nc*=|nZmOFi7KC!S%7?ci6y!4b##gYSc&b`t6KA@J#A*VMK)3e zjJLI!2CMo;P|q&q++`{|WqIZ^{7_8eb|7UVGl4|G?_3Wu(|8&4^VG+}Ow|?oY2tq- zOn|*_`icNEcA%sS0@3v}C}aCkejY1mq_P7J#=rU}rdeoJTAMoy76cFaYJoOX5U(iH zFjlQgi9fll+k}m=>ni{d9{}`-9r(1d94#)Qf9vNpO4}#puW>tUCh!;=w`(#{bu&T> zm)$iA97ulg4pY@8h0OjQ^x15sySB*e^Ju?g2pz>0v3p@C-r~B`tlDJ=!Ie4@hg!wg>F9jn-tEdwISTbr@g*5LN>z4n7b$S6zbb1q9ieZwq@g!eSjBa|AU+b zV8aRMkQ4xKOy8guh(pFst)HkIb)=HssoGY53EYlRT) z1c``}YiF6(gi>Ka;2pv6(}86HK`9;l*s)7tK5bOpvvZ+TYK~>LvVjnDS6QV%o=ALz zr%M%cL(iVJ(9vxg@7_hmbdPJWpy;v;x{L^<83$#stmbH z^-Vn@t!z>ZS5`ApFF4j*yzNY~tRy8vX_nfBxkj9`v-7hXuGFnH705BRrE%oez ze5>9oQMU_|8}G>>1z;{lk_9<)%w~TB$DxC)FWC!_dv}B5?UZ?m0{N;%EaNDQsY*M# zWZf5}KK84Ik>#CR_sd5sH5_tQ4mZ7?ABf123a6TZz&8kPR`hGee8e`iKmx=f({}~8 zx}uqu+Lsb}tnD+(LmISmY4mIUxB{>^FkzZXZ;&y=J(d&W-70Khady&;bIU{8j8>0+ z`tX=rELej@qINFxnUC(O_h}fwVFzfN>BVD%4l4kbZOL;*xKtj}{7j^u&&%GCl+*X% z1#~3}n3Nr*dY3(m&zqdyXJafV%BA$F!1NAa=3CR$dqIAEIpX%kvQ;|Qx50y%2#+DX zS~Hz@l9R{3JYJ$4J%jOsv5hvt>-!DPnf6kOH_^J^de_B z8jhZzy`&BqclskbIe&Cihn+S)&(LQ3S~(n#qe)%XiY*jZ4mmmbgIp}& zALwt1wGl^VfhtHgcduHn9!Mp`?PS}{mw3|s6wxtr1E?Saj@5LatdDKmwtUQZ&6juc zcphrz;-N)&s@%l#jjUh`yMZCA)PRT0C51Lw0q3NNLrT6tetI)FYNm;Ur67M!G-u|> zqG{^NurPh};%1N2p*)AEX!FpRXYI2+Yd(I?7pdhJ2rI@bXIy79w&~`u`e2~+QwuB5 za~%$|l!ZEtg``UNWc$MXP@^HcGmu{t0E3)E+;eHld7%s z#IW0n9qJw!j^NQ$LWZe&?XQ0Cult3Ukx2K+w2UVGj~*oOTNpMOVnX3uS*FGuaQf>3Lg9NC3NqER|kZvm@UKASU=<`a)N> zoD62qsw0%`#mh`-7S@twIy_{HRp9p7jZ1I1cv$3Ks_F+X!$J9gW39UmXb%|`RkAw> z&-0JvF1*0z1zl4{oo}52dM|Iq&DBL0v{0yEROIcXuVycLfGtdT%p#wzukXQ+e{rLu z5#_p7E?w$A5#$Bo=VPRW&~--6x1|q*d?XSLlzM$$wZs@b>Wt5~4(?*ltUOl2=iMR= z^ckI=ZEF_EJg56ANIGO}@{f9yg~-jmVAimX;dO(Hka0;)b&Z7Qif&F8pZyorG$IMg z0=zGdc-JEdCN_L)Uc zKkC#}8(+6=z~gM|p=-wnpz2@*!hejV;O?H48}>X~p57cDF3|$%YPT#?$4KrUx)8wx zt~w_+;Zq-FwPm!uCw_2;5~37PKY3doXk|TdfI8Q!OPztXY-MDLzk8dr*u8)8$=}I{ z^JUR^HqW^BDnF4G%{>#m9U0tF~Q9Q6J#U>xXRrAa09piRP;1YoXN&H4pY2Sv$< zav^>DJ@gq$@l3#44)IJAam0>@1V7u~Y*rox)hc)!WX8Y90>q(UEVel-_K+m2`YhL6 zCF?wn%ru{0V4u%-Nu7*2Y7-}P*lUt?o=-ft&yZ|2$?hIARiMRh?k#V7e;s97@d4|P z(L=`%h-q78+bs+G>7-V->u%jp_ZV2O@T-~vhMOX45oSqU`w4Y-uZ-7Ssu(ampT^^Z z2Rl-dwE3_KAXa-0CUEbFS@y~$UXjnSD7Z&Ms$7?Ohx&D60w;iPXK(nF5_$Pyr5X0` zZ0^mdcvqLw!Q*33^_<=E#2da2oR@A1gMDAT_=~E>B9@cHY8kk9B5XFBT%Z^GpzJ9B z^rGGkzw(SreHy&y8cciCiyG5a*I*%)nJa7)$_+~|n;}E7iL8xon~seq%)YHe85$0! z>h+SsiSr=|w2;D;&bahyCZCzm8XL$JjEC+*WA24n#>IuP<^JqIg$;L0#+spOSd@wb zcwf3NGI!pa^znGJ9pR|;RMP&s0=oT{Fy(;r=G(vz{*UUs6o6J^Oa#S0$Sngl*#C$R z*x7eudz*P904Bm zZ@;Fm`j)#JQ4Jp@Cb14#-f3*(a0D~{pwU78)$6T|CQJM&;a20qd4$?L!^2p2TfJO* zMNi)6;aR63^r`k<=;&R?D%It%-IK;7cUH&sC&i0zd!z*FDT~xDte1^+vQ4VW_O`%A z71lbhK~ilIuV0$+kT%@i;H3nib+aoPvXbF?^&ZtUUv4kMJWb#_roi8VhVYcrtZ&)i z?`n1hq7cO6ris(l0;=niXzv$R9sIk`&!FSebx_7=Jk>qt?|-VH!vR&ZlgngH_qO|r znDB!>(`gT0b`~aALx=T}7SJ9FsKY+k_ze{Gh-4FDDz1gByS!86uPhR4?5rf8;}Wpf zhv3cP`QBu<`dB8Xju;ij#^=zA2-Z=ToH_gUP2#CDtKYG4rv^I~3K^-E<})2B2e4K+ zz21wh&hn}I2$nHf(4jA~dFfoJpzJ1nY(GAUNt>v%g5O0a2IPTeI5;`HLQKEJ)IKp1 zGvl%`Xl)|!D;jBVM+#vAmL3k4us51%Nk!FlSlT?Z^^nm|!oWejVvz^2uy4E~gtlNS zar@bykl>4vTrY`^-`7}6e7O04V>jH@KveOa0Lr)7QOU5UQfA9ez`zB z_HVTcb~n~=iP&FZwQBaYZsy|lbZEW?2;Y}0BA}^3h;T#L8?Sj_guHn50MTyn1gZ#%o>>ADg@?&mZW9nT*s=*z&KW z_tSHrp1T_IYHZs~N*%YF5nk`D`8_$;50&~j0KOU0n)LVk*Nl?gTT+Shf#Gquy9kNt zP@$#(>vH|qWzogw^YY9UJ?8uYADQ{KwHt-*RqE^~evD6nx`Prs_`s~Mkd=4?4dHVK{ssYz<#+sEFVr!v=HRf2GNr#`>+f*I|@q8*AO zrfCuYzW47U*$w%_*2;u)4Vl4FUb)wj#IMJWv+QM zp!+zTt}4dpf55^~ zyZG?EcD%dc)V0)fP`Q|S?BrD^2OQ@N#64tn;idR`__!7S4F`3tu|o7 z+jIpE%}$^Jh7Hk!$PX$RcSB_JTus?Z@u;s@up27^5eDqu?S5iiqR3aHRZw3Z z&-gabspe#j^OLo6g^0aFqW=M$Ad*N8~ihWA+IE=D3J7t(7*yAU0 zTpaI)pf>@iE)Ftm8n+V9w-4ts#&7v{Zgj##SCjmBf%{G9`%&}qV!n7vR2 z$TZHH&-yfFq4sh1POK>1UWXAo{;2P9j%{JmCnINgW)xwudC52M#2k2#F{mTOgE&dN zS{me2JE>Y=3o`;@oHEtPS=TtNzo4=XBr|>%F8AV?T%y&1pPXg9mYZ|#L>3Z_Ev-Xa z-wBO-X~YIhUX7lvQss1D*uRYiqi_8^(+XBd@4E__^OKA$v!Ap+K9+V>Z~!m0E1Zt1 zz3nh`hG?ei^Q7`l^zAzkcY0P4d_s%u)Y@)SL&x^CqiYDT_0TnYdC})GT^wkcp9^n2 zFqw^pmNSc6HzD~h*_L!_h2j}m8@bV9miqDYH-c{5Qg^G9ln)*K;VY7r0!VUe2OVBD z!J7QNJ%9L0Nw}G&hyE+Jlzip}j4y+=i6%*|wIm9~OR70;<37JP0E?IlEp5ugs;e_v zi*r^tO+M5Nk?l}XROJOKaHTPY=2FNh_#(3U83M{sr{5h8)I$5Z*@)TS*HN58c`%R* z)imWnHfhvndm~@|GymKjBPp%z0VdV*^8;BTewzOFjeBOHP5K{u*H+TD1BW@A`Yz08 ztYx9ec*w_F9;R9%Rk8z;Q?k+#Y^e*q1#@+odtD7*0TUi7eB_*>uucB(BMMtms07ei z6L{ADt9spe$mpWXqGNaDt!QUC)PmQF7d4}VYLu>cr+i0gmwWlFIXg;!IVfBIY0Qv{ zQp<0HK`ie}=shWdfzM6YL*rnKh5|4L%WTa-1prk1uG7Ku1|&oGTtk~uWZg1y5I{3< zTX2Tq43FgOq2_b{Y#K`cSZTbN0)uAk($}qY1D{Jz86}{kpPob7bd~gM9l;VbZfJQ` zjW_kR>|DlLwEfirtCKGE<;FGROu4TP9AuNL?<|D|B>S3eIa;!>j5@gxnduiB_HI=a%=`H6fJcyjuq zN*MXait0?=Wsbz2Pqc|pNZqecQg}A6pi_jtB`I{pRoOhpXBaCvtHjW{rZE2T|bi3U{l%Y=KRdWg| z+*@#~SXt&vHSoClt{m8rGXwY9-Y;{LAvb7tlk95vcUwaAC* z!OVEtAyl<-A2e{^E3ha%!&-!<6l}@nOq! zPm*l}fL!^i$Krc*WZKFPc%R)S+PL5*wf!1ER&J$at-Th7ec5)bb?g={YI-a2%SsS2 z#pg@RT*&mU#+W%|hD8om1?jz7@Ji8j=kB}$EZxBpOmxe54_Lc(n|TkL?3PHl%pS!ULzPRbH7MRABPTgP z8LbA5wT&Ye&{nv{WR_jbUy{5cNG}b2T_s+=f^j5UzS6?@f=tKpDOy2Nqjrapjaf}r zrlvU?MD2_*)$};=zomj<*%h%`sC^mv!EU7VDFr7g&a={BX3eKO_+^wwkzhEvP)5F+ z*8~$5DYBXhS>%7kC4&DYolGV|6CYw4vsa(=SSerw?bQ0de4pW*38b*Y{TJJAbkuSs z0RWb?rlqopJ`$N5bZ_BO#byBED;Ld|dVe%jYq$7J4q~xf z7{1(7EDM663_*WT@h^geqND_!xF6C z4`|)c*j&4>{hTWdX(PvEU0(WLjhi7P#>>ZFfRQV~kV$WX-ex|H6Ms@GI{Bldobi~z zEyY76$WWThSiA8tA=ET6Ka)W2E#?P063t_EiK5$g9#SvKN_Vs03(M1#3|FqJQ9bhHmQ-Vi+!6ayPFZ3#RaR4)2|IV6;2SIT%6KogkrX@y0ZIP#7W7T7sj-zY0hRs5l~!~HuSBU;P{qQm_@mskR5+y*4VRh zFKvP%nWr2Ki=P#ty2x91kLa$DS^Y>&kp|$*`ZA7doW~<-_?AgGiT7&uD>Q9)#5x*R z@8xnAZi?0ot5>%cF7?o=x}ef&`7pdMx04cKN!(KZBL-)upELM(m)0PGy$h#w(50VS$hs5}O4a&?kQu z#Ay<|F~)N*`f|0NY3uPDGwd9R6=5XTwfVfpV*fhC%&nC_&oxN~eRlMcsrS3jTL*;_ z#8gAdTfk*#gjPEjvlRT0X_|mhEy|(B!UpVYEGCzvd#EkI(42OFH63itX0 zDGkqVqIns)ve}azXsHtm$rGjOBwrby1$(JR8oKd9(&s)*vmo!Dqo9;QMCL)8EBP9Z#L^$HN-NewS zv+rYU^G9@_s-Oy1gD;Pc5~LN*qDx15n-~`ZEY4y7#zOhB0j#<-S1fy(3~87YEc7^~ z^PX?ycITuRaWS3BT^a0A$$z~SLv|e)lX(uy338!2^Ko|!G`)U{57TTwM+&UFZ1TQr ztbpuzu<0<+Rh~*t-C2sL>Mz^P?%YZiGnb6wZ;(%wa{P>Gi(;_mI_XtF!=rri96rj2 z$S#JaylvHATLUr9T!miLnMi`41WjK~xM-o_31AJP`S#skhI4e|O)}=`i_e-6#hMAF zKf_d_G#}Jpc!$%w?Gcm3lbEG=MzAF1TzU>#>IaJCZ z31(pd@gVF%e*G8{>lWr{@WKC5uT2L7rHBcZdebvC27iz{NZKCeFv7Y80`+6+U+M|4ilZL46Y#cOBvz(kb28C|g-%F_8CbVCK9I=E{Jml?3jO0YFO#_> zOcciyWL=R{mJ{-eq$@tRSf$6oAfL54m94Z{(T!qI*gN6Oc*#t+*Ohxh@knt5XC!ab zDd(hJiQk8JNp`9fPSC(MLOFEh^t>qejTqriujmK3En7KLqN?wX0)dF;e_A&r{SvZN z0@{}v_U`n(GdKmN7JKOHi@j$Pjog;9{Lb*CY#%`TGRyU@~R zryltR@U*D}Mvf9HG$g?2*7+lHa{|E7EsA6ZL+J`UttYwB%v;Z{r%ivf=V$QM%>!$_aJ|3^|gr?LBqlX zkLa*?*1F)?J5q$8vMCg}V2NvAS0{&d220YzVt4-__c3YbCYxbi&za4c>8|%LLr(vD z(}?*zeBH0$9Gb0l6zI#AuU!>cFj*dv&S0Jes}EG4i>sG8mo*=vtw2qOJv!1$7bui4 zj|CikkqzNSKhZ2ZOf8IZt^Ftv9z8T8W3rjmaI&!He*D>@FcWTq?5R z=*g5UrUOq-jNv9}f!Oij-!WF73bv~(dP0YxNFnivc#np{nTAwush+miwy*Q=Ar%sA zsMHP@xM8cIo=TNC56Fd*fODOgz*kseEtYF))nMR4a+B3$E?5!OOaK>Wx8#ISx>S>C z1S%^jA%L+27b}nbh>m*_$6pBd-ebpUcPCca-T(q7sJB8qnlPlgi;*?r=8Qwx*6u5| zkn~C4t(T$5U<0>(`MvaSC4b7ky6rN&#d zgNe$d7#-T!hBOZRO@S;ehX|GjLd!eckC?0iEliKDqq>K;ZDw=u;~dcmq5 zrjX+}ug|g9IQ<{iUMel0CPDsX1jVtKa|vDk2RSNWgO&}NH=0V19NSrVK9Ural{RK< zhPe__xu{L4w=#5QpCEW<(xfD6V~5pWL$H4vTUoyqIvcL?@2D!!n?6m8zZ7L>*q>_u z-BZ({wvq$YV3S|e;JI+nzcunf&QkIVQ6T7WVKI<@Y*+=+PuSd3wdm0k*up;$CP^u$ zRuXmTlJ_3fqP7yJV{=UZ)*awRC5GdtH2W=>_jQC-mvDDA{A1P5(QM|xOwihuviivLToWeo(}8^@A+_H@K-+5bt|2={xWNqh>Y>C)zYKZ) zd+E8^V!GRR!TGCSjM zQl`?8hsa2)t@9ZRj7E%wTC6IlVdJOY88=p%`=OM;Fz6*)R`b-9^voj4|AB=a%ORY_ zWmqlPr;X+r9Ki5<;rZ2$^vo4!S7k{}at&JL;>~0Ctu$q_7fOr44Hu)j;=8Vm@B(X} zaX0=qQm||#^YPi)jF2Y?mNP$-(q{UeLE=Mxhl6YliLR`y<&79{?W5vxRvfy7_Ut~V zw4{)v)rX{GKIletnI)HCe5*~rK;y6aFMg7+?ZIzYT{I$Nx96q+cDe^Yr3i}YQ8Sp+ z`gUIeCn?^t<&bSrlaBh@AV>BO%a^8cYBvDUQ+fDzn&RFWpf9QMk}sq6R4*h6*q=v; z)izTeP06VR77ws{VH$0sipaXR{$%A(;4C`Qfu`WN-rmiUE80`)(^I1MGW&VMq z24BXzb5zqE{^xYcF-^S!2$cFe>w}Mn6*NNecK;to=i$!w`u_3QVvD_nAhBody;sCm ztHf4q?SrZsL1HJwUa@!WqS|A`3RSa3wTBo*wFjj~=l7?-zaZC@>-D+s`?;U@`~7g4 zEPPiltyrgJXvAz8;!QFkB#ZeXBt?Lc23Pa^pxUYu`A}yy%;ZyKIv85#;JGxAk(m>E z*uRqE*$;QHoLT`zNLr6z0~n);I}KquzZ_#;i zqlLzo=W)>7D4}%Ubf8%|rHNi2O5OmBh zu$(+M+rP$D$)4aTVSC#3oWCT^CuPXj%Uo`#d#KN*qjnq|XWsXoN46#1;pAaBpfF~@ z?m`$}D>k$6|0{n$jDx+oJA*Lm^w>MMk5gBY$Jh&cdIvE}oOsdZE+qxlm-Ym~qJoKmg3#VW9eGb+$Y{Wr8moQN=|~?h@zdsL z*3DnPFORn0-_BsQ5A*gAJzkFMkfN4NAoE#q`m%TmV@&<`VSLh{OmJxG{o~p;(ecsD z?5Vv7Bd?bnc)cVZ$VDj$!9;aAWm9GRh&Kk`FjY#DjDws_9S@N@bQj=1 zGPKE-3^kbHpDpSwc#3PZSFx@R#At)4kr-~trgGlQYIFvt?*%NBgE!B*6*G6DS)4n$ zp@dlx_M|h6byJ>;cZ*;xbbG{$?*BFdr)luAtxHhKGxd9yB59G#X636Lf-&1$8u#FhUb_a*t5mR*g-mrPwX z{(=4J^C9J@E_ox)96$yx&9CivqrWs%RE3y7rmtp2@9Xf|-#ra2c3LkeV!$(Jbc%7F z;9DNFv}kgo`(c>nhtcg`J|C?VO+D3))%uwhEbs{gs}m|zAE{|H|SY{rJhHv zu|u1`!Ut@5wo@~&-HbVsHv@+6Eb4tLmb~wv!c!FGq`GD3{X3N6dI=y4OWAAEpm{C( zF$bwQM*l*P-ZULYd>s(SKyi1qfEq{fm_O01T>1L%m8Z&}%5bhc1apimWj#ttV$ZQ3J+0;`{Ou@ybT%@1#+*Wt6)lul3j z?=pBnstb-?cwtk@he|(V`K@T|b#K*Ik!}|=GQ3cbvdIE9RlOD;i`fQ?eZUrjB z(pd9J9B5=HLfHe(K5DRK^vy+e1KHUD0NUgQdu#oD`)=w=R2gTg52GPa66gSZIwMKl zjg@c;w12Czk4*OFnAo`fpP?Cga4oJT*Gz15wP>--)bA?Y^DJGu;PqZv0|qxY&`B-8iux6x8UOh-jnhC}&**N}wVT&B{P`YLC@)ci{%MvRryAfAYo9C@VgW zr2EP*kNPJ2z+4Q4ZLmihE5`^w7!A;Jf{D54?SNacUVo6gS&}HTYUC+h5&tnf9Db=o z4-QDDw`)L4?hIrNL|Ap77S^=Ac5t|37k&G5r1ZT`Eic-}>8I03VKdqahA$TYD~*$* zgmN6)0@OWcLU&%vRB3c5H^#2KxH$`CJaaSY1ZT`81}h3I;jKvLXC8S{+I;hU@d%&F z58t-D;q@)Q!O*8FPQSQ6iqK-lcs@#%%?zK){42GqcqH zprCg^A=DT}&jc}Fe0mIJ&9J0U%uWQ)%$E{s`zF0tH+npr!XZ%GQ;~)C)|=qwkLgM; z63aumK|isaSVf~Z5?8eq+6#_X^3Ry|$}VhO+FjZfl?|Q*!0f)ht8=zIq=E^02E9fU z$>5?cG+d(c%KD8&yBATbx1_~}o5zH-iXoSC1<+=snI$c$LkD%;;S@}QIY z{-NTPkN&d8me&PMrfX;mwmLh1$c&dXHfu@=>QuXsT54TIUh(jx!=jgBSz;nSm&=-6 zG<#0c8;op4T$+ z)%ZI(vuF*T#${_vVrh56Ht3HSKTET^cb zjy}gXQPVT=luE?jm>42b2jAa&Kct|Pe&7D`XU=eNWY{S~ zVSdW^?i^=zWvUBzF;Iq$-{!(7$8Y?xUFO>aqfNqm5S?4gF8gpG*OcR~Jh6TmT2y&g zHt*29kuWH3oF7U90RR-)I(Dz4bZI9jY?PqIV)_FyvMCPhCG~sMZfagKUP@1Yy-!__ zl5tVjd|RS-R#K-ie0!vg#NW1LBQ-KFUjD*x!p2c7VQANfCpbS>`Ih3G{m-8R&Si6j zOyzVDzx#!tJ*lWk<_JG6y16j<@mY07dIJmT*=cox2+HzzbZ)V@8l`!Y?u`t;~neUgCOb6 z2QDK$Dj3^bO=*{L=jSm^{H^L}7{yKQkxkvQoWZ?kqIM&<(~G~)H~6^hLurd*Q3167 z`uL2LHptqC)Nf^VAK0|nJ=X?51B|E#s0E2D@m#%W8u`T6)r(QoCWK#}`vo`ve?U!5 zluyR5mbb+BxC(R(?|ANb!`sk%9S;H~YXzt`D)!@Yhqi4d; z1(;^e;k!(Uw1vpt`RbbKQBdXst)n2&K}I`*7Z+zg1$&qT3|NETr%RRjX+moU7y6h9WiH1T5nH@#3ytxh7(dL=bUB;= zjiOj;)cTq^p!g~EWL*Z|MrmXLkT)J&pz7g~l+F;I_{QPzUIqnN0U`GvU=2^Fxz4Yf z1r|P7E)0sd+zxc)>zF%G;Vbr~0p^^_OGS((vF?<5c;WVS$clO6!b&A25&`x=l_BML62=`QqwdUUtk!eY2ysj0|&-^m#fqBVyW`ZOAZ=6Zn{Sjs5%SF}CI>8wj&a??czSgr~2$i{lhO5!?}`D{CKTiArq?FN36jcX#)t{EuZJRDxB+Fz$LblB$zkxJ;}v0I}Df3z~Q;x!3aU zO`oZY>mvmKBLf6bS{)Xt+kR{KfElps)8EicDsD-!)H{l&bbWb8nV(^Q#W-LOds)Qc zd-BAc;y$f@!CvWiKM2bDgWP%Y@o#qdJl`D@#dy%ok_6mLJxQ!x z8@l)MlZ$XGoNIsJ6tDk@k51lm}-9JHsj zF&ErSA!#2d%O^f)p@qjNORt@(Zq@2iuGK_1F5nk-1ww7A%Q{qWg#o1Q zS8!^T^u8Rz?p0rS1Y5+JFsBqOQ9I{IJEm4Q7YFcxkPUZzvU9w9{}Epx^-YGfQ;r|6 zk%WWJuT?4?JEblI?oLszY#rFKsNM=N-mKn??&>?y><}sVfZlGYddHPxo4>RU^4968 z#7ci&`mSc(JB)CY#>-BVwG4KyE{1=IJ$c9>Qnk_M(-O@V>ibQcj($IFuP@!-B*+f& zlX%s~z6|33qHa>2&dyS*|F@4=T*7cX;0fO!x!g72B$8c@<>tgHYIw5>?zs+`jQCFB zj{W8*X+$sR$kFDyW#6K4XC|jwear`zN`0`_Aoexy!-SacZp4pUAP$yK9fC7P=Ux?R z`LyXEtx4t0jn9S}!j^Y^6&1K$(TY5q-u_5%hc|Y$)28&=Ju-QDGbH9Umv~~7Zpe4~ z?wf|%7QP*^%3R1A_(nOYQONu)eJ0E$gOr0WC`7HX4UiIIf(LnW7%u74(N$BSaNumiiqs6~C&5^t+FoC;z8`{w>1>wy!&Oox#^c<@*BcHbng9_PD!@4r_z4$>NELctP5hA-0%N z6f_FUhBS;n08`b*7dSLJ@Np*Nz>}zd{2)^)KxMWRwLAKJ^S*4#Qi9M9;dX;+saKfg zRGVzRD7i*Jz5qIk5jD10%WFe^sM0+gVo+o~_dny*Raetv_m?QqTGxs|=qH;e!{C#M zq*k4!dvwZOV&N-VNHdAqg#Jkh)0Qp~67@*LNebLvW~Rf2Gn#4jshYw5pe>*Yg-Bn}Yr zjnE(5$E8*Bb4oAT=amkvolXuw!i{z?{GTFsoqPB;0WYNqe)KCAJ*1`vi4hlSs!Pnp&dwSZDoZN|9InhJEU8_@`MKfq&aw>- z1DZx+KtC=n=n77uO|QP4)rKM6V&N5Wbs5Rt677|xY7rEuqYxohP}DFeT4V0Bh3zb;nl=6_MF%vTP!1>R1HQcdCJ zV6wH(cGAImOw)?uBx2z-DZfne^JhT$mYo z@t5?hdWwy>k=0Gk&Vl{np&pB4+$dxClmzwY(+X-?_kyp{*QmO=-M_Q|bM}v^8CJU1 zfEgu*_N=syHLR5CuhPoe*IW_V-EV1XJ==@nd1BV!)kqVlHv!hpWqh+|<^w^pwltI-Uz-yoQm%%kK?`~M*ShPTT*|gpc z9lNZ)O$@S?$*^0j2(;tQ8YDjX#cIU}P= z7PIL3GKFB538ego^YGfTMxcA+3HSZVAo@Zuc6FsDqOdJ3`R|sw?*pCBQwSfy4;?>U z&hmDU{}>{1NK2yd`mlDK=N0p66a5Kk=|B3sc<%d z$^Qa0y6x#ynr5FT892qa=cb z@_UPkptW>wVyaY@G2d#WaGUtscNSxF00Y37vvDz%y|aBEik!sh24yTqhCN&}W;YFq z66Cjkc>2U5v0c2>at&$d>906tCSwyg zOYckJ$(BCe3Y(5r8p(P9-R6`gNB21XLGH$E!_D|-l(Ts^et?}C9V>_y{~;|n8{C>5 za`~%!N+GkNZ~q0y8b6KRYm@GA4L`n&+t+tESOTpa#g8&M8bQxXf9WtASAMFD5+!gD z7LoOoTzuc5NFWmpRq>GAv z<8bUmY}UY21HB`~Gi(3u)xW18iFMzj;rGja7K$~vpahL*EQS4hz+=P?08OC##H>U_ zZ8?-K;Vbv=(_TPd35<6qQ!=d-m|skgiEsiJ-3)35z(>J{UPAh^&t^TON;{x!PH$#N zPg_cz-La0ZuVa$P{D-Ny)xkkgCDE-lS>J)cGGU)`f%rLU#hBF`x-i3}z+Hr~trBFR zlMFz3`RbShfgyR^brG4_tod1+vQD2Y-ViCqJf;kR@sP7bZRJNjXLbtQ#)Fi|l<>d# z8|h39hQf?Pf_P?D(MTcd06)}+4v}h9Mr}$P^)8qn$u9Vr9NOn(uo0aI&oS3j($`ZE zl9QIvmJ{Gh^SNau(V`*^G`8ep?K&H#&{RD&)mN1tbJ4k48%bZEIksB=hd85~Cqe%B}mx_)&=uOHa3t zMAF$0J>Qs2kwN{3p|W-G$ip>*Qae}WOfERS=>t9z2r)+m|e_2bQ z>7JHh-MdF7M54?jTOuAwPENCHIaD^cm0`rp#o!$RA#1+Th!=9FtpSRUN~S37Su}3j z*MA!jE78r4K)IKj_Q2g3=e}DJe>TS1fhclv5WMM2X7iD5$0kq94qB&w$M1;;PNJdA-ttBnMF{~L zLROV%t-N}}<<0ZAnJTR_90?7^8iMswYT%>qHUd)U^<}2sqEerXb`=%vx<>_Klu*dfMHT-_i_7*FuA273ALHTUUG3fXv7=dxDtq4OGo9?VgxIbc_>4jaa4^4ch)&7nn9kg zUHSYQ9U7t8q-B87ZH~5y3A185NiKe;wtLnkDjVsrrM2Z-;dYd�g{j22?fo#oBiX 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 F{ts*A`Ue02 diff --git a/src/enemies/Zagger.ts b/src/enemies/Zagger.ts index cf94ec7..d92bc5b 100644 --- a/src/enemies/Zagger.ts +++ b/src/enemies/Zagger.ts @@ -56,7 +56,7 @@ export class Zagger extends Enemy { 5, 52, 0.8, - 'saucer_explosion' + 'spider_explosion' ); } From 939b14ce19e1991c74a324cba2c6845c6f8f25ef Mon Sep 17 00:00:00 2001 From: "SINISTER\\paul" Date: Mon, 15 Sep 2025 21:24:02 -0400 Subject: [PATCH 24/32] Move spiders to new fourth level. --- src/Game.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Game.ts b/src/Game.ts index 16df3aa..bc854f0 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -178,7 +178,6 @@ export class Game { this._clock, [ new Level([ - new ZigZagWave(this._audioPlayer, this._world, this._clock), new SpinnerWave(this._audioPlayer, this._world, this._clock), new SimpleWave(this._audioPlayer, this._world, this._clock) ]), @@ -190,6 +189,11 @@ export class Game { new SimpleWave(this._audioPlayer, this._world, this._clock), new SecondWave(this._audioPlayer, this._world, this._clock), new SplitterWave(this._audioPlayer, this._world, this._clock) + ]), + new Level([ + new SpinnerWave(this._audioPlayer, this._world, this._clock), + new SpinnerWave2(this._audioPlayer, this._world, this._clock), + new ZigZagWave(this._audioPlayer, this._world, this._clock) ]) ] ); From a479275b1230464d38fab35290f636e36c254978 Mon Sep 17 00:00:00 2001 From: "SINISTER\\paul" Date: Sat, 20 Sep 2025 18:48:10 -0400 Subject: [PATCH 25/32] Cleanup. --- src/enemies/Spinner.ts | 2 +- src/enemies/Zagger.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/enemies/Spinner.ts b/src/enemies/Spinner.ts index 0714f47..a294110 100644 --- a/src/enemies/Spinner.ts +++ b/src/enemies/Spinner.ts @@ -255,7 +255,7 @@ export class Spinner extends Enemy { } } -export module Spinner { +export namespace Spinner { export enum Bias { Left, Right diff --git a/src/enemies/Zagger.ts b/src/enemies/Zagger.ts index d92bc5b..e3e8cb3 100644 --- a/src/enemies/Zagger.ts +++ b/src/enemies/Zagger.ts @@ -218,7 +218,7 @@ export class Zagger extends Enemy { } } -export module Zagger { +export namespace Zagger { export enum State { Entering, SwoopAndReturn, From f58b1efc1f09c8eb3d4c7b63909ab257d5c57207 Mon Sep 17 00:00:00 2001 From: "SINISTER\\paul" Date: Sat, 20 Sep 2025 19:11:22 -0400 Subject: [PATCH 26/32] Add web shot tests. --- test/shots/Web.spec.ts | 144 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 test/shots/Web.spec.ts diff --git a/test/shots/Web.spec.ts b/test/shots/Web.spec.ts new file mode 100644 index 0000000..4651e5f --- /dev/null +++ b/test/shots/Web.spec.ts @@ -0,0 +1,144 @@ +import {describe} from 'mocha'; +import {expect} from 'chai'; + +import {Dimensions} from "../../src/Dimensions"; +import {Point} from '../../src/Point'; +import {ScoreCounter} from "../../src/ScoreCounter"; +import {Web} from '../../src/shots/Web'; +import {World} from '../../src/World'; + +import {AudioPlayerStub} from "../stubs/AudioPlayerStub"; +import {ClockStub} from '../stubs/ClockStub'; +import {PlayerStub} from "../stubs/PlayerStub"; + +describe('Web', () => { + describe('#tick()', () => { + let audioPlayer: AudioPlayerStub; + let clock: ClockStub; + let world: World; + + beforeEach(() => { + audioPlayer = new AudioPlayerStub(); + clock = new ClockStub(); + world = new World(new Dimensions(480, 640), new ScoreCounter()); + }); + + it('should move the web directly downwards', () => { + const web = new Web(audioPlayer, clock, world, new Point(5, 10)); + web.tick(); + expect(web.coordinates.x).to.be.equal(5); + expect(web.coordinates.y).to.be.above(10); + }); + + it ('should advance sprite frames over time', () => { + const web = new Web(audioPlayer, clock, world, new Point(5, 10)); + expect(web.imageDetails.currentFrame).to.be.equal(0); + web.tick(); + expect(web.imageDetails.currentFrame).to.be.equal(0); + clock.addSeconds(1); + web.tick(); + expect(web.imageDetails.currentFrame).to.be.greaterThanOrEqual(1); + const lastFrame = web.imageDetails.currentFrame; + clock.addSeconds(1); + web.tick(); + expect(web.imageDetails.currentFrame).to.be.greaterThanOrEqual(lastFrame); + }); + + it ('should stop advancing frames at the maximum frame', () => { + const web = new Web(audioPlayer, clock, world, new Point(5, 10)); + expect(web.imageDetails.currentFrame).to.be.equal(0); + web.tick(); + clock.addSeconds(5); + web.tick(); + expect(web.imageDetails.currentFrame).to.be.greaterThanOrEqual(1); + const lastFrame = web.imageDetails.currentFrame; + clock.addSeconds(1); + web.tick(); + expect(web.imageDetails.currentFrame).to.be.equal(lastFrame); + }); + + it('should remain active while it remains within the world', () => { + const web = new Web(audioPlayer, clock, world, new Point(5, 10)); + web.tick(); + expect(web.isActive).to.be.true; + }); + + it('should become inactive when it leaves the world', () => { + const web = new Web(audioPlayer, clock, world, new Point(5, world.dimensions.height - 1)); + web.tick(); + expect(web.isActive).to.be.false; + }); + + it('should hit an active player within collision distance', () => { + const player = new PlayerStub(world, new Point(10, 10)); + world.addActor(player); + + let hit: boolean = false; + player.onHit(damage => { hit = true }); + + const web = new Web(audioPlayer, clock, world, new Point(10, 10)); + web.tick(); + expect(hit).to.be.true; + }); + + it('should not hit an active player outside collision distance', () => { + const player = new PlayerStub(world, new Point(100, 100)); + world.addActor(player); + + let hit: boolean = false; + player.onHit(damage => { hit = true }); + + const web = new Web(audioPlayer, clock, world, new Point(10, 10)); + web.tick(); + expect(hit).to.be.false; + }); + + it('should hit the player with damage equal to 1', () => { + const player = new PlayerStub(world, new Point(10, 10)); + world.addActor(player); + + const hitFor: number[] = []; + player.onHit(damage => { hitFor.push(damage) }); + + const web = new Web(audioPlayer, clock, world, new Point(10, 10)); + web.tick(); + expect(hitFor).to.be.eql([1]); + }); + + it('should become inactive after it has made a successful hit', () => { + const player = new PlayerStub(world, new Point(10, 10)); + world.addActor(player); + + const hitFor: number[] = []; + player.onHit(damage => { hitFor.push(damage) }); + + const web = new Web(audioPlayer, clock, world, new Point(10, 10)); + web.tick(); + expect(web.isActive).to.be.false; + }); + + it('should become inactive if it makes an unsuccessful hit', () => { + const player = new PlayerStub(world, new Point(10, 10)).ignoreHits(); + world.addActor(player); + + const web = new Web(audioPlayer, clock, world, new Point(10, 10)); + web.tick(); + expect(web.isActive).to.be.false; + }); + + it('should remain active when there is no player', () => { + const web = new Web(audioPlayer, clock, world, new Point(10, 10)); + web.tick(); + expect(web.isActive).to.be.true; + }); + + it('should play a sound on the first tick', () => { + const playedSounds: string[] = []; + audioPlayer.onPlay((soundName: string) => playedSounds.push(soundName)); + + const web = new Web(audioPlayer, clock, world, new Point(10, 10)); + web.tick(); + expect(playedSounds.length).to.be.above(0); + }); + }); +}); From 81133fa57fc52d7d1d800ccda139424a87426a91 Mon Sep 17 00:00:00 2001 From: "SINISTER\\paul" Date: Sun, 28 Sep 2025 19:11:12 -0400 Subject: [PATCH 27/32] Fix collision boundaries for web. --- src/shots/Web.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/shots/Web.ts b/src/shots/Web.ts index 57d9f4f..e328927 100644 --- a/src/shots/Web.ts +++ b/src/shots/Web.ts @@ -30,7 +30,9 @@ export class Web extends Shot { } getCollisionMask(actor: Actor): Bounds[] { - return [new Bounds(-5, 5, -5, 5)]; + const offset = this._currentFrame / 10.0 * 45; + + return [new Bounds(-offset, offset, -offset, offset)]; } getDamageAgainst(actor: Actor): number { @@ -50,9 +52,8 @@ export class Web extends Shot { this._firstTick = false; } - //this._currentFrame = 1;//(this._currentFrame + 1) % 4; // TODO: schedule growing size this._scheduler.scheduleOperation( - 'incraseSize', + 'increaseSize', 250, () => { this._currentFrame = Math.min(this._currentFrame + 1, 9); } ); From 15e4ca240e3670ae037496d2ce2c54cd0dd8d841 Mon Sep 17 00:00:00 2001 From: "SINISTER\\paul" Date: Mon, 29 Sep 2025 20:58:57 -0400 Subject: [PATCH 28/32] Make spider level a bit harder. --- src/enemies/Zagger.ts | 17 +++++++++++------ src/shots/Web.ts | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/enemies/Zagger.ts b/src/enemies/Zagger.ts index e3e8cb3..131e13e 100644 --- a/src/enemies/Zagger.ts +++ b/src/enemies/Zagger.ts @@ -123,20 +123,26 @@ export class Zagger extends Enemy { switch (turns) { case 0: const linePath = new LinePath(this._location, lowestPoint, [new ScheduledAction(0.50, PathAction.Fire)]); - this._currentPath = linePath.getPathForSpeed(8); + this._currentPath = linePath.getPathForSpeed(10); this._pathPosition = 0; this._state = Zagger.State.Swooping; break; case 1: const midPoint = new Point(Math.floor(random(10, 430)), random(300, 500)); - this._currentPath = new LineSegmentPath([this._location, midPoint, lowestPoint], [new ScheduledAction(0.50, PathAction.Fire)]).getPathForSpeed(8); + this._currentPath = new LineSegmentPath( + [this._location, midPoint, lowestPoint], + [new ScheduledAction(0.25, PathAction.Fire), new ScheduledAction(0.55, PathAction.Fire)] + ).getPathForSpeed(10); this._pathPosition = 0; this._state = Zagger.State.Swooping; break; case 2: const midPoint1 = new Point(Math.floor(random(10, 430)), random(300, 400)); const midPoint2 = new Point(Math.floor(random(10, 430)), random(400, 500)); - this._currentPath = new LineSegmentPath([this._location, midPoint1, midPoint2, lowestPoint], [new ScheduledAction(0.50, PathAction.Fire)]).getPathForSpeed(8); + this._currentPath = new LineSegmentPath( + [this._location, midPoint1, midPoint2, lowestPoint], + [new ScheduledAction(0.30, PathAction.Fire), new ScheduledAction(0.60, PathAction.Fire)] + ).getPathForSpeed(10); this._pathPosition = 0; this._state = Zagger.State.Swooping; break; @@ -147,7 +153,7 @@ export class Zagger extends Enemy { const lowestPoint = new Point(Math.floor(random(10, 430)), 500); const swoopDownPath = new LinePath(this._location, lowestPoint, []); const swoopReturnPath = new LinePath(lowestPoint, this._homePosition, [new ScheduledAction(0.50, PathAction.Fire)]); - this._currentPath = swoopDownPath.getPathForSpeed(8).concat(swoopReturnPath.getPathForSpeed(8)); + this._currentPath = swoopDownPath.getPathForSpeed(8).concat(swoopReturnPath.getPathForSpeed(10)); this._pathPosition = 0; this._state = Zagger.State.SwoopAndReturn; } @@ -194,8 +200,7 @@ export class Zagger extends Enemy { } // Drop shots at random times while waiting. - //if (this._state != Zagger.State.Entering && this._location.y < 500 && random(0, 100) === 0) { - if (this._state === Zagger.State.Waiting && random(0, 100) === 0) { + if (this._state === Zagger.State.Waiting && random(0, 30) === 0) { this.dropWeb(); } diff --git a/src/shots/Web.ts b/src/shots/Web.ts index e328927..29a9a46 100644 --- a/src/shots/Web.ts +++ b/src/shots/Web.ts @@ -59,7 +59,7 @@ export class Web extends Shot { ); this._scheduler.executeDueOperations(); - const speed = 10; + const speed = 12; for (let step = 0; step < speed; step++) { this.move(Direction.Down); From 4ff1bc088dddbcd5fd5d0f29d4b684bd9145bc89 Mon Sep 17 00:00:00 2001 From: "SINISTER\\paul" Date: Mon, 27 Oct 2025 20:29:51 -0400 Subject: [PATCH 29/32] Rename ZigZag/Zagger to Spider. --- src/Game.ts | 4 +- src/enemies/{Zagger.ts => Spider.ts} | 53 +++++++++++----------- src/waves/{ZigZagWave.ts => SpiderWave.ts} | 42 ++++++++--------- 3 files changed, 49 insertions(+), 50 deletions(-) rename src/enemies/{Zagger.ts => Spider.ts} (84%) rename src/waves/{ZigZagWave.ts => SpiderWave.ts} (72%) diff --git a/src/Game.ts b/src/Game.ts index bc854f0..68f7fb7 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -16,13 +16,13 @@ import {Scheduler} from './timing/Scheduler'; import {ScoreCounter} from './ScoreCounter'; import {SecondWave} from './waves/SecondWave'; import {SimpleWave} from './waves/SimpleWave'; +import {SpiderWave} from "./waves/SpiderWave"; import {SpinnerWave} from './waves/SpinnerWave'; import {SpinnerWave2} from './waves/SpinnerWave2'; import {SplitterWave} from './waves/SplitterWave'; import {StarField} from './StarField'; import {TextInterlude} from './TextInterlude'; import {World} from './World'; -import { ZigZagWave } from "./waves/ZigZagWave"; export class Game { private readonly _joystick: Joystick; @@ -193,7 +193,7 @@ export class Game { new Level([ new SpinnerWave(this._audioPlayer, this._world, this._clock), new SpinnerWave2(this._audioPlayer, this._world, this._clock), - new ZigZagWave(this._audioPlayer, this._world, this._clock) + new SpiderWave(this._audioPlayer, this._world, this._clock) ]) ] ); diff --git a/src/enemies/Zagger.ts b/src/enemies/Spider.ts similarity index 84% rename from src/enemies/Zagger.ts rename to src/enemies/Spider.ts index 131e13e..4fa313c 100644 --- a/src/enemies/Zagger.ts +++ b/src/enemies/Spider.ts @@ -20,7 +20,7 @@ import {Scheduler} from '../timing/Scheduler'; import {Web} from '../shots/Web'; import {World} from "../World"; -export class Zagger extends Enemy { +export class Spider extends Enemy { public static readonly InitialHealth: number = 1; private readonly _clock: Clock; @@ -30,11 +30,11 @@ export class Zagger extends Enemy { private _currentFrame: number = 0; private _currentPath!: PathEntry[]; private _pathPosition!: number; - private _state: Zagger.State; + private _state: Spider.State; constructor(audioPlayer: AudioPlayer, world: World, clock: Clock, startingPoint: Point, homePosition: Point) { - super(audioPlayer, world, startingPoint, Zagger.InitialHealth); - debug('Zagger constructor'); + super(audioPlayer, world, startingPoint, Spider.InitialHealth); + debug('Spider constructor'); this._clock = clock; this._scheduler = new Scheduler(clock); @@ -42,11 +42,11 @@ export class Zagger extends Enemy { this._homePosition = homePosition; this.prepareEntryPath(homePosition); - this._state = Zagger.State.Entering; + this._state = Spider.State.Entering; this.advanceCurrentFrame(); } - get state(): Zagger.State { + get state(): Spider.State { return this._state; } @@ -82,7 +82,7 @@ export class Zagger extends Enemy { } tick(): void { - debug('Zagger.tick'); + debug('Spider.tick'); super.tick(); if (!this._isActive) { @@ -122,21 +122,24 @@ export class Zagger extends Enemy { const turns = random(0, 2); switch (turns) { case 0: + console.log('we are here 3'); const linePath = new LinePath(this._location, lowestPoint, [new ScheduledAction(0.50, PathAction.Fire)]); this._currentPath = linePath.getPathForSpeed(10); this._pathPosition = 0; - this._state = Zagger.State.Swooping; + this._state = Spider.State.Swooping; break; case 1: + console.log('we are here 4'); const midPoint = new Point(Math.floor(random(10, 430)), random(300, 500)); this._currentPath = new LineSegmentPath( [this._location, midPoint, lowestPoint], [new ScheduledAction(0.25, PathAction.Fire), new ScheduledAction(0.55, PathAction.Fire)] ).getPathForSpeed(10); this._pathPosition = 0; - this._state = Zagger.State.Swooping; + this._state = Spider.State.Swooping; break; case 2: + console.log('we are here 5'); const midPoint1 = new Point(Math.floor(random(10, 430)), random(300, 400)); const midPoint2 = new Point(Math.floor(random(10, 430)), random(400, 500)); this._currentPath = new LineSegmentPath( @@ -144,21 +147,23 @@ export class Zagger extends Enemy { [new ScheduledAction(0.30, PathAction.Fire), new ScheduledAction(0.60, PathAction.Fire)] ).getPathForSpeed(10); this._pathPosition = 0; - this._state = Zagger.State.Swooping; + this._state = Spider.State.Swooping; break; } } else { + console.log('we are here 6'); // Swoop and return to home. const lowestPoint = new Point(Math.floor(random(10, 430)), 500); const swoopDownPath = new LinePath(this._location, lowestPoint, []); const swoopReturnPath = new LinePath(lowestPoint, this._homePosition, [new ScheduledAction(0.50, PathAction.Fire)]); this._currentPath = swoopDownPath.getPathForSpeed(8).concat(swoopReturnPath.getPathForSpeed(10)); this._pathPosition = 0; - this._state = Zagger.State.SwoopAndReturn; + this._state = Spider.State.SwoopAndReturn; } if (random(0, 1) === 0) { + console.log('we are here 1'); this.dropWeb(); } } @@ -167,43 +172,37 @@ export class Zagger extends Enemy { // Choose the next path to follow once we've reach the end of the current path. if (this._pathPosition >= this._currentPath.length) { switch (this._state) { - case Zagger.State.Entering: - this._state = Zagger.State.Waiting; + case Spider.State.Entering: + this._state = Spider.State.Waiting; break; - case Zagger.State.SwoopAndReturn: - this._state = Zagger.State.Waiting; + case Spider.State.SwoopAndReturn: + this._state = Spider.State.Waiting; break; - case Zagger.State.Swooping: + case Spider.State.Swooping: this._scheduler.scheduleOperation( 'enter', 3000, () => { // Determine our path back to our home position after. const worldDimensions = this._world.dimensions; - const zaggerStartingPoint = new Point( + const spiderStartingPoint = new Point( Math.floor(random(0, worldDimensions.width-50)), -20 ); // TODO: We might be able to call prepareEntryPath here instead. - const linePath = new LinePath(zaggerStartingPoint, this._homePosition, []); + const linePath = new LinePath(spiderStartingPoint, this._homePosition, []); this._currentPath = linePath.getPathForSteps(10); this._pathPosition = 0; - this._state = Zagger.State.Entering; + this._state = Spider.State.Entering; } ); - //this._state = Zagger.State.Waiting; - this._state = Zagger.State.SwoopComplete; + this._state = Spider.State.SwoopComplete; break; } return; } - // Drop shots at random times while waiting. - if (this._state === Zagger.State.Waiting && random(0, 30) === 0) { - this.dropWeb(); - } - // Follow the current path. switch(this._currentPath[this._pathPosition].action) { case PathAction.Move: @@ -223,7 +222,7 @@ export class Zagger extends Enemy { } } -export namespace Zagger { +export namespace Spider { export enum State { Entering, SwoopAndReturn, diff --git a/src/waves/ZigZagWave.ts b/src/waves/SpiderWave.ts similarity index 72% rename from src/waves/ZigZagWave.ts rename to src/waves/SpiderWave.ts index 54322c0..65cefe5 100644 --- a/src/waves/ZigZagWave.ts +++ b/src/waves/SpiderWave.ts @@ -8,19 +8,19 @@ import {Point} from '../Point'; import {Scheduler} from '../timing/Scheduler'; import {Wave} from './Wave'; import {World} from "../World"; -import {Zagger} from '../enemies/Zagger'; +import {Spider} from '../enemies/Spider'; -export class ZigZagWave implements Wave { +export class SpiderWave implements Wave { private readonly _audioPlayer: AudioPlayer; private readonly _world: World; private readonly _clock: Clock; private _numberOfEnemiesLeftToDeploy: number = 15; - private readonly _swoopers: Map = new Map(); + private readonly _swoopers: Map = new Map(); private readonly _scheduler: Scheduler; constructor(audioPlayer: AudioPlayer, world: World, clock: Clock) { - debug('ZigZagWave constructor'); + debug('SpiderWave constructor'); this._audioPlayer = audioPlayer; this._world = world; this._clock = clock; @@ -30,7 +30,7 @@ export class ZigZagWave implements Wave { this._scheduler.scheduleOperation( 'deploy', 0, - () => { this.deployZagger() } + () => { this.deploySpider() } ); } @@ -41,31 +41,31 @@ export class ZigZagWave implements Wave { } tick(): void { - debug('ZigZagWave.tick'); + debug('SpiderWave.tick'); this._scheduler.executeDueOperations(); } scheduleNextSwoop(swoopIndex: number) : void { // If no swooper has been chosen yet, or if the last chosen swooper is dead or waiting, schedule a new swoop. - if (!this._swoopers.has(swoopIndex) || (!this._world.activeEnemies.includes(this._swoopers.get(swoopIndex)!) || this._swoopers.get(swoopIndex)!.state === Zagger.State.Waiting)) { + if (!this._swoopers.has(swoopIndex) || (!this._world.activeEnemies.includes(this._swoopers.get(swoopIndex)!) || this._swoopers.get(swoopIndex)!.state === Spider.State.Waiting)) { if (this._world.activeEnemies.length > 0) { const timeTillSwoop = random(100, 1000); this._scheduler.scheduleOperation( `next swoop ${swoopIndex}`, timeTillSwoop, () => { - const waitingZaggers = this._world.activeEnemies.filter(enemy => (enemy as Zagger).state === Zagger.State.Waiting); - if (waitingZaggers.length > 0) { - const zagger = waitingZaggers[random(waitingZaggers.length-1)] as Zagger; - this._swoopers.set(swoopIndex, zagger); + const waitingSpiders = this._world.activeEnemies.filter(enemy => (enemy as Spider).state === Spider.State.Waiting); + if (waitingSpiders.length > 0) { + const spider = waitingSpiders[random(waitingSpiders.length-1)] as Spider; + this._swoopers.set(swoopIndex, spider); - zagger.swoop(); + spider.swoop(); this.scheduleNextSwoop(swoopIndex); } else { - // No waiting zaggers, so try again later. + // No waiting spiders, so try again later. this._scheduler.scheduleOperation( `next swoop ${swoopIndex}`, 500, @@ -81,25 +81,25 @@ export class ZigZagWave implements Wave { this._scheduler.scheduleOperation(`next swoop ${swoopIndex}`, 0, () => this.scheduleNextSwoop(swoopIndex)); } - deployZagger(): void { + deploySpider(): void { if (this._numberOfEnemiesLeftToDeploy > 0) { const worldDimensions = this._world.dimensions; - const zaggerStartingPoint = new Point( + const spiderStartingPoint = new Point( Math.floor(random(0, worldDimensions.width-50)), -20 ); - const zaggerHomeX = (5-(this._numberOfEnemiesLeftToDeploy-1)%5) * 80; - const zaggerHomeY = 100 + Math.floor((this._numberOfEnemiesLeftToDeploy-1)/5) * 70; - const zaggerHomePosition = new Point(zaggerHomeX, zaggerHomeY); - const zagger = new Zagger(this._audioPlayer, this._world, this._clock, zaggerStartingPoint, zaggerHomePosition); - this._world.addActor(zagger); + const spiderHomeX = (5-(this._numberOfEnemiesLeftToDeploy-1)%5) * 80; + const spiderHomeY = 100 + Math.floor((this._numberOfEnemiesLeftToDeploy-1)/5) * 70; + const spiderHomePosition = new Point(spiderHomeX, spiderHomeY); + const spider = new Spider(this._audioPlayer, this._world, this._clock, spiderStartingPoint, spiderHomePosition); + this._world.addActor(spider); this._numberOfEnemiesLeftToDeploy--; this._scheduler.scheduleOperation( 'deploy', 50, - () => { this.deployZagger() } + () => { this.deploySpider() } ); } else { From 3b89b564ee4e3dea06d60712ffe4b8f34a547109 Mon Sep 17 00:00:00 2001 From: "SINISTER\\paul" Date: Mon, 27 Oct 2025 20:37:09 -0400 Subject: [PATCH 30/32] Remove console.log statements. --- src/enemies/Spider.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/enemies/Spider.ts b/src/enemies/Spider.ts index 4fa313c..fa9e06e 100644 --- a/src/enemies/Spider.ts +++ b/src/enemies/Spider.ts @@ -122,14 +122,12 @@ export class Spider extends Enemy { const turns = random(0, 2); switch (turns) { case 0: - console.log('we are here 3'); const linePath = new LinePath(this._location, lowestPoint, [new ScheduledAction(0.50, PathAction.Fire)]); this._currentPath = linePath.getPathForSpeed(10); this._pathPosition = 0; this._state = Spider.State.Swooping; break; case 1: - console.log('we are here 4'); const midPoint = new Point(Math.floor(random(10, 430)), random(300, 500)); this._currentPath = new LineSegmentPath( [this._location, midPoint, lowestPoint], @@ -139,7 +137,6 @@ export class Spider extends Enemy { this._state = Spider.State.Swooping; break; case 2: - console.log('we are here 5'); const midPoint1 = new Point(Math.floor(random(10, 430)), random(300, 400)); const midPoint2 = new Point(Math.floor(random(10, 430)), random(400, 500)); this._currentPath = new LineSegmentPath( @@ -152,7 +149,6 @@ export class Spider extends Enemy { } } else { - console.log('we are here 6'); // Swoop and return to home. const lowestPoint = new Point(Math.floor(random(10, 430)), 500); const swoopDownPath = new LinePath(this._location, lowestPoint, []); @@ -163,7 +159,6 @@ export class Spider extends Enemy { } if (random(0, 1) === 0) { - console.log('we are here 1'); this.dropWeb(); } } From 246748ae46691576f149ea5264c70f380ed3941d Mon Sep 17 00:00:00 2001 From: "SINISTER\\paul" Date: Sun, 16 Nov 2025 10:37:47 -0500 Subject: [PATCH 31/32] Add some tests for Spider class. --- test/enemies/Spider.spec.ts | 80 +++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 test/enemies/Spider.spec.ts diff --git a/test/enemies/Spider.spec.ts b/test/enemies/Spider.spec.ts new file mode 100644 index 0000000..66a1985 --- /dev/null +++ b/test/enemies/Spider.spec.ts @@ -0,0 +1,80 @@ +import {describe} from 'mocha'; +import {expect} from 'chai'; + +import {AudioPlayer} from "../../src/devices/AudioPlayer"; +import {Bullet} from "../../src/shots/Bullet"; +import {Dimensions} from "../../src/Dimensions"; +import {Point} from '../../src/Point'; +import {Spider} from '../../src/enemies/Spider'; +import {ScoreCounter} from '../../src/ScoreCounter'; +import {World} from '../../src/World'; + +import {AudioPlayerStub} from "../stubs/AudioPlayerStub"; +import {ClockStub} from "../stubs/ClockStub"; +import {ActorStub} from "../stubs/ActorStub"; + +describe('Spider', () => { + let audioPlayer: AudioPlayer; + let clock: ClockStub; + let scoreCounter: ScoreCounter; + let world: World; + + beforeEach(() => { + audioPlayer = new AudioPlayerStub(); + clock = new ClockStub(); + scoreCounter = new ScoreCounter(); + world = new World(new Dimensions(480, 640), scoreCounter); + }); + + describe('#hitBy()', () => { + it('should return true', () => { + const spider = new Spider(audioPlayer, world, clock, new Point(5, 10), new Point(100, 100)); + const actor = new ActorStub(world, new Point(5, 10)); + world.addActor(actor); + + expect(spider.hitBy(actor, 1)).to.be.true; + }); + }); + + describe('#tick()', () => { + it('should de-activate after health reaches zero', () => { + const bullet = new Bullet(audioPlayer, world, new Point(5, 10)); + world.addActor(bullet); + + const spider = new Spider(audioPlayer, world, clock, new Point(5, 10), new Point(100, 100)); + spider.hitBy(bullet, Spider.InitialHealth); + spider.tick(); + expect(spider.isActive).to.be.false; + }); + + it('should remain active after hit if health remains above zero', () => { + const bullet = new Bullet(audioPlayer, world, new Point(5, 10)); + world.addActor(bullet); + + const spider = new Spider(audioPlayer, world, clock, new Point(5, 10), new Point(100, 100)); + spider.hitBy(bullet, Spider.InitialHealth / 2); + spider.tick(); + expect(spider.isActive).to.be.true; + }); + + it('should add an explosion when it is destroyed', () => { + const bullet = new Bullet(audioPlayer, world, new Point(5, 10)); + world.addActor(bullet); + + const spider = new Spider(audioPlayer, world, clock, new Point(5, 10), new Point(100, 100)); + spider.hitBy(bullet, Spider.InitialHealth); + spider.tick(); + expect(world.activeExplosions.length).to.be.equal(1); + }); + + it('should increment the score when it is destroyed', () => { + const bullet = new Bullet(audioPlayer, world, new Point(5, 10)); + world.addActor(bullet); + + const spider = new Spider(audioPlayer, world, clock, new Point(5, 10), new Point(100, 100)); + spider.hitBy(bullet, Spider.InitialHealth); + spider.tick(); + expect(scoreCounter.currentScore).to.be.above(0); + }); + }); +}); From 4b50d78c6fe30be2fae5b97e3414fd666f728218 Mon Sep 17 00:00:00 2001 From: "SINISTER\\paul" Date: Sun, 16 Nov 2025 10:52:25 -0500 Subject: [PATCH 32/32] Add missing tests for Point. --- test/Point.spec.ts | 93 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 test/Point.spec.ts diff --git a/test/Point.spec.ts b/test/Point.spec.ts new file mode 100644 index 0000000..e8ece6c --- /dev/null +++ b/test/Point.spec.ts @@ -0,0 +1,93 @@ +import {describe} from 'mocha'; +import {expect} from 'chai'; + +import {Point} from '../src/Point'; + +describe('Point', () => { + describe('#ctor()', () => { + it('defines a position', () => { + const point = new Point(3, 4); + expect(point.x).to.be.equal(3); + expect(point.y).to.be.equal(4); + }); + }); + + describe('#withX()', () => { + it('returns a new Point with the specified x coordinate', () => { + const point = new Point(3, 4); + const newPoint = point.withX(10); + expect(newPoint.x).to.be.equal(10); + expect(newPoint.y).to.be.equal(4); + }); + }); + + describe('#withY()', () => { + it('returns a new Point with the specified y coordinate', () => { + const point = new Point(3, 4); + const newPoint = point.withY(10); + expect(newPoint.x).to.be.equal(3); + expect(newPoint.y).to.be.equal(10); + }); + }); + + describe('#translate()', () => { + it('returns a new Point translated by the specified amounts', () => { + const point = new Point(3, 4); + const newPoint = point.translate(2, 3); + expect(newPoint.x).to.be.equal(5); + expect(newPoint.y).to.be.equal(7); + }); + }); + + describe('#up()', () => { + it('returns a new Point moved upwards by the specified amount', () => { + const point = new Point(3, 4); + const newPoint = point.up(2); + expect(newPoint.x).to.be.equal(3); + expect(newPoint.y).to.be.equal(2); + }); + }); + + describe('#down()', () => { + it('returns a new Point moved downwards by the specified amount', () => { + const point = new Point(3, 4); + const newPoint = point.down(2); + expect(newPoint.x).to.be.equal(3); + expect(newPoint.y).to.be.equal(6); + }); + }); + + describe('#left()', () => { + it('returns a new Point moved left by the specified amount', () => { + const point = new Point(3, 4); + const newPoint = point.left(2); + expect(newPoint.x).to.be.equal(1); + expect(newPoint.y).to.be.equal(4); + }); + }); + + describe('#right()', () => { + it('returns a new Point moved right by the specified amount', () => { + const point = new Point(3, 4); + const newPoint = point.right(2); + expect(newPoint.x).to.be.equal(5); + expect(newPoint.y).to.be.equal(4); + }); + }); + + describe('#equals()', () => { + it('returns true for points with the same coordinates', () => { + const point1 = new Point(3, 4); + const point2 = new Point(3, 4); + expect(point1.equals(point2)).to.be.true; + }); + + it('returns false for points with different coordinates', () => { + const point1 = new Point(3, 4); + const point2 = new Point(5, 4); + const point3 = new Point(3, 6); + expect(point1.equals(point2)).to.be.false; + expect(point1.equals(point3)).to.be.false; + }); + }); +});