diff --git a/package-lock.json b/package-lock.json index eae7f806f6..e2f6c16475 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "ansyn-src", - "version": "4.1.1", + "version": "5.1.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -9678,7 +9678,7 @@ "dependencies": { "ansi-colors": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", "dev": true, "requires": { @@ -20664,6 +20664,11 @@ "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", "dev": true }, + "ts-keycode-enum": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/ts-keycode-enum/-/ts-keycode-enum-1.0.6.tgz", + "integrity": "sha512-DF8+Cf/FJJnPRxwz8agCoDelQXKZWQOS/gnnwx01nZ106tPJdB3BgJ9QTtLwXgR82D8O+nTjuZzWgf0Rg4vuRA==" + }, "ts-node": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", diff --git a/package.json b/package.json index ca2bd7c437..e4da98a59e 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "proj4": "^2.5.0", "rxjs": "~6.5.4", "shpjs": "^3.6.3", + "ts-keycode-enum": "^1.0.6", "tslib": "^2.0.0", "wellknown": "^0.5.0", "zone.js": "~0.10.2" diff --git a/src/app/@ansyn/ansyn/assets/important/adani.jpg b/src/app/@ansyn/ansyn/assets/important/adani.jpg new file mode 100644 index 0000000000..868d9559eb Binary files /dev/null and b/src/app/@ansyn/ansyn/assets/important/adani.jpg differ diff --git a/src/app/@ansyn/ansyn/assets/important/aviv.JPG b/src/app/@ansyn/ansyn/assets/important/aviv.JPG new file mode 100644 index 0000000000..1e8e83b093 Binary files /dev/null and b/src/app/@ansyn/ansyn/assets/important/aviv.JPG differ diff --git a/src/app/@ansyn/ansyn/assets/important/dana.JPG b/src/app/@ansyn/ansyn/assets/important/dana.JPG new file mode 100644 index 0000000000..76bf234e42 Binary files /dev/null and b/src/app/@ansyn/ansyn/assets/important/dana.JPG differ diff --git a/src/app/@ansyn/ansyn/assets/important/elor.JPG b/src/app/@ansyn/ansyn/assets/important/elor.JPG new file mode 100644 index 0000000000..7faade1593 Binary files /dev/null and b/src/app/@ansyn/ansyn/assets/important/elor.JPG differ diff --git a/src/app/@ansyn/ansyn/assets/important/pini.JPG b/src/app/@ansyn/ansyn/assets/important/pini.JPG new file mode 100644 index 0000000000..95639f1af8 Binary files /dev/null and b/src/app/@ansyn/ansyn/assets/important/pini.JPG differ diff --git a/src/app/@ansyn/ansyn/assets/important/rom.JPG b/src/app/@ansyn/ansyn/assets/important/rom.JPG new file mode 100644 index 0000000000..ed64683a10 Binary files /dev/null and b/src/app/@ansyn/ansyn/assets/important/rom.JPG differ diff --git a/src/app/@ansyn/ansyn/assets/important/tzahi.jpg b/src/app/@ansyn/ansyn/assets/important/tzahi.jpg new file mode 100644 index 0000000000..9ecfc54855 Binary files /dev/null and b/src/app/@ansyn/ansyn/assets/important/tzahi.jpg differ diff --git a/src/app/@ansyn/ansyn/assets/important/yehonatan.jpg b/src/app/@ansyn/ansyn/assets/important/yehonatan.jpg new file mode 100644 index 0000000000..b54ce9b6e5 Binary files /dev/null and b/src/app/@ansyn/ansyn/assets/important/yehonatan.jpg differ diff --git a/src/app/@ansyn/ansyn/assets/important/yuval.jpg b/src/app/@ansyn/ansyn/assets/important/yuval.jpg new file mode 100644 index 0000000000..934d74e905 Binary files /dev/null and b/src/app/@ansyn/ansyn/assets/important/yuval.jpg differ diff --git a/src/app/@ansyn/ansyn/modules/core/components/angle-filter/angle-filter.component.ts b/src/app/@ansyn/ansyn/modules/core/components/angle-filter/angle-filter.component.ts index 29ee09080f..18c8394eec 100644 --- a/src/app/@ansyn/ansyn/modules/core/components/angle-filter/angle-filter.component.ts +++ b/src/app/@ansyn/ansyn/modules/core/components/angle-filter/angle-filter.component.ts @@ -1,8 +1,7 @@ import { Component, ElementRef, HostBinding, HostListener, OnDestroy, OnInit, Renderer2 } from '@angular/core'; import { ImageryCommunicatorService, - toDegrees, - toRadians + toDegrees } from '@ansyn/imagery'; import { ContextMenuShowAngleFilter, diff --git a/src/app/@ansyn/ansyn/modules/core/core.module.ts b/src/app/@ansyn/ansyn/modules/core/core.module.ts index c8bcfb51c2..925638ee18 100644 --- a/src/app/@ansyn/ansyn/modules/core/core.module.ts +++ b/src/app/@ansyn/ansyn/modules/core/core.module.ts @@ -17,6 +17,7 @@ import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { CacheInterceptorsService } from './services/http-request-cache/cache-interceptors.service'; import { CacheRequestService } from './services/http-request-cache/cache-request.service'; import { CredentialsService } from './services/credentials/credentials.service'; +import { KeysListenerService } from "./services/keys-listener.service"; @NgModule({ imports: [ @@ -33,7 +34,8 @@ import { CredentialsService } from './services/credentials/credentials.service'; AreaToCredentialsService, CredentialsService, CacheRequestService, - { provide: HTTP_INTERCEPTORS, useClass: CacheInterceptorsService, multi: true} + KeysListenerService, + {provide: HTTP_INTERCEPTORS, useClass: CacheInterceptorsService, multi: true} ], exports: [ AnsynTranslationModule, diff --git a/src/app/@ansyn/ansyn/modules/core/services/keys-listener.service.spec.ts b/src/app/@ansyn/ansyn/modules/core/services/keys-listener.service.spec.ts new file mode 100644 index 0000000000..85d4b8dbc6 --- /dev/null +++ b/src/app/@ansyn/ansyn/modules/core/services/keys-listener.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { KeysListenerService } from './keys-listener.service'; + +describe('KeysListenerService', () => { + let service: KeysListenerService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(KeysListenerService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/@ansyn/ansyn/modules/core/services/keys-listener.service.ts b/src/app/@ansyn/ansyn/modules/core/services/keys-listener.service.ts new file mode 100644 index 0000000000..0a64ed9aa2 --- /dev/null +++ b/src/app/@ansyn/ansyn/modules/core/services/keys-listener.service.ts @@ -0,0 +1,43 @@ +import { Injectable, Output, EventEmitter } from '@angular/core'; +import { fromEvent } from "rxjs"; + +@Injectable({providedIn: 'any'}) +export class KeysListenerService { + + @Output() keyup = new EventEmitter(); + @Output() keypress = new EventEmitter(); + @Output() keydown = new EventEmitter(); + events: string[] = ['keypress', 'keyup', 'keydown']; + + constructor() { + this.events.forEach(eventName => fromEvent(document, eventName).subscribe(($event: KeyboardEvent ) => + { + if (this.isElementNotValid($event)) { + return; + } + this[`${eventName}`].emit($event) + })) + } + + isElementNotValid($event: KeyboardEvent) { + const {activeElement} = ($event.view).document; + return this.isElementInput(activeElement) || this.isTimePicker(activeElement); + } + + isElementInput(activeElement) { + return activeElement instanceof HTMLInputElement; + } + + isTimePicker(activeElement) { + const {className} = activeElement; + return className.includes('owl') || className.includes('title'); + } + + keyWasUsed(event: KeyboardEvent, key: number): boolean { + return event.which === key; + } + + keysWereUsed(event: KeyboardEvent, keys: number[]): boolean { + return keys.some(key => this.keyWasUsed(event, key)); + } +} diff --git a/src/app/@ansyn/ansyn/modules/easter-eggs/pacman-popup/pacman-popup.component.html b/src/app/@ansyn/ansyn/modules/easter-eggs/pacman-popup/pacman-popup.component.html new file mode 100644 index 0000000000..993367a97c --- /dev/null +++ b/src/app/@ansyn/ansyn/modules/easter-eggs/pacman-popup/pacman-popup.component.html @@ -0,0 +1,2 @@ +
+

Score:0

diff --git a/src/app/@ansyn/ansyn/modules/easter-eggs/pacman-popup/pacman-popup.component.less b/src/app/@ansyn/ansyn/modules/easter-eggs/pacman-popup/pacman-popup.component.less new file mode 100644 index 0000000000..7453536048 --- /dev/null +++ b/src/app/@ansyn/ansyn/modules/easter-eggs/pacman-popup/pacman-popup.component.less @@ -0,0 +1,79 @@ +.grid { + display: flex; + flex-wrap: wrap; + width: 560px; + height: 560px; + border: solid black; + background-color: black; +} + +.grid div { + width: 20px; + height: 20px; +} + +.pac-dot { + background-color: white; + border: 7px solid black; + box-sizing: border-box; + border-radius: 50%; +} + +.wall { + background-color: blue; +} + +.power-pellet { + background-color: rgb(140, 206, 255); + border-radius: 50%; + border: 3px solid black; + box-sizing: border-box; +} + +.ghost { + box-sizing: border-box; + background-color: darkred; + border-radius: 0px !important; + border: none !important; +} + +@length: 2; +@random: `Math.ceil(Math.random() * (@{length}))`; +@contentRoot: '/assets/important'; + +@blinkyImages: 'yehonatan.jpg', 'tzahi.jpg'; +@randomBlinkyimage: extract(@blinkyImages,@random); + +.blinky { + background: url('@{contentRoot}/@{randomBlinkyimage}'); +} + +@pinkyImages: 'aviv.jpg', 'pini.jpg'; +@randomPinkyimage: extract(@pinkyImages,@random); + +.pinky { + background: url('@{contentRoot}/@{randomPinkyimage}'); +} + +@inkyImages: 'elor.jpg', 'yuval.jpg'; +@randomInkyimage: extract(@inkyImages,@random); + +.inky { + background: url('@{contentRoot}/@{randomInkyimage}'); +} + +@clydeImages: 'adani.jpg', 'rom.jpg'; +@randomClydeimage: extract(@clydeImages,@random); + +.clyde { + background: url('@{contentRoot}/@{randomClydeimage}'); +} + +.pac-man { + background-color: yellow; + border-radius: 50%; +} + +.scared-ghost { + background: url('@{contentRoot}/dana.JPG'); +} diff --git a/src/app/@ansyn/ansyn/modules/easter-eggs/pacman-popup/pacman-popup.component.spec.ts b/src/app/@ansyn/ansyn/modules/easter-eggs/pacman-popup/pacman-popup.component.spec.ts new file mode 100644 index 0000000000..0a47019f43 --- /dev/null +++ b/src/app/@ansyn/ansyn/modules/easter-eggs/pacman-popup/pacman-popup.component.spec.ts @@ -0,0 +1,38 @@ +import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing'; + +import { PacmanPopupComponent } from './pacman-popup.component'; +import { Store, StoreModule } from "@ngrx/store"; +import { IStatusBarState, statusBarFeatureKey, StatusBarReducer } from "../../status-bar/reducers/status-bar.reducer"; +import { selectActiveMapId, selectMapsList, selectOverlayOfActiveMap } from "@ansyn/map-facade"; +import { of } from "rxjs"; + +describe('PacmanPopupComponent', () => { + let component: PacmanPopupComponent; + let fixture: ComponentFixture; + let store: Store; + let statusBarState: IStatusBarState; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + StoreModule.forRoot({ + [statusBarFeatureKey]: StatusBarReducer + }) + ], + declarations: [ PacmanPopupComponent ] + }) + .compileComponents(); + })); + + beforeEach(inject([Store], (_store: Store) => { + store = _store; + + fixture = TestBed.createComponent(PacmanPopupComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/@ansyn/ansyn/modules/easter-eggs/pacman-popup/pacman-popup.component.ts b/src/app/@ansyn/ansyn/modules/easter-eggs/pacman-popup/pacman-popup.component.ts new file mode 100644 index 0000000000..62298fcd06 --- /dev/null +++ b/src/app/@ansyn/ansyn/modules/easter-eggs/pacman-popup/pacman-popup.component.ts @@ -0,0 +1,335 @@ +import { Component, OnInit, ViewChild, ElementRef, Renderer2, OnDestroy, AfterViewInit } from '@angular/core'; +import { KeysListenerService } from "../../core/services/keys-listener.service"; +import { AutoSubscription, AutoSubscriptions } from "auto-subscriptions"; +import { filter, tap } from "rxjs/operators"; +import { Store } from "@ngrx/store"; + +export enum KEY_CODE { + RIGHT_ARROW = 39, + LEFT_ARROW = 37, + DOWN_ARROW = 40, + UP_ARROW = 38 +} + +export enum BOARD_CELLS { + PAC_DOT, + WALL, + GHOST_LAIR, + POWER_PALLET, +} + +class Ghost { + className: string; + startIndex: number; + speed: number; + currentIndex: number; + previousIndex: number; + isScared: boolean; + timerId: number; + + constructor(className, startIndex, speed) { + this.className = className; + this.startIndex = startIndex; + this.speed = speed; + this.currentIndex = startIndex; + this.previousIndex = startIndex; + this.isScared = false, + this.timerId = NaN + } +} + +@Component({ + selector: 'ansyn-pacman-popup', + templateUrl: './pacman-popup.component.html', + styleUrls: ['./pacman-popup.component.less'] +}) +@AutoSubscriptions() +export class PacmanPopupComponent implements OnInit, OnDestroy, AfterViewInit { + + title = 'angular-pacman'; + width = 28; + scoreValue = 0; + + // layout + // legend + // 0 - pac dot + // 1 wall + // 2 - ghost lair + // 3 - power pellet + // 4 - empty + layout = [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, + 1, 3, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 3, 1, + 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, + 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 0, 1, 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 1, 1, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 0, 1, 1, 4, 1, 1, 1, 2, 2, 1, 1, 1, 4, 1, 1, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 0, 1, 1, 4, 1, 2, 2, 2, 2, 2, 2, 1, 4, 1, 1, 0, 1, 1, 1, 1, 1, 1, + 4, 4, 4, 4, 4, 4, 0, 0, 0, 4, 1, 2, 2, 2, 2, 2, 2, 1, 4, 0, 0, 0, 4, 4, 4, 4, 4, 4, + 1, 1, 1, 1, 1, 1, 0, 1, 1, 4, 1, 2, 2, 2, 2, 2, 2, 1, 4, 1, 1, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 0, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 0, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 0, 1, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, + 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, + 1, 3, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 3, 1, + 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, + 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, + 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, + 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, + 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + + squares = []; + pacmanCurrentIndex = 490; + // define all the 4 ghosts + ghosts = [ + new Ghost('blinky', 348, 150), + new Ghost('pinky', 376, 200), + new Ghost('inky', 351, 120), + new Ghost('clyde', 379, 300) + ] + + @ViewChild('grid') grid: ElementRef; + @ViewChild('score') score: ElementRef; + @ViewChild('result') result: ElementRef; + isPacmanAlive = true; + + constructor(private renderer: Renderer2, + public keyListenerService: KeysListenerService, + private elem: ElementRef, + protected store$: Store) { + + } + + /** + * Actively listen to the keyup event in order to move the pacman + */ + @AutoSubscription + KeyUpEvent$ = () => this.keyListenerService.keyup.pipe( + filter(() => this.isPacmanAlive), + tap($event => { + this.renderer.removeClass(this.squares[this.pacmanCurrentIndex], 'pac-man'); + if ($event.keyCode === KEY_CODE.RIGHT_ARROW) { + if ( + this.pacmanCurrentIndex - this.width >= 0 && + !this.squares[this.pacmanCurrentIndex + 1].classList.contains('wall') && + !this.squares[this.pacmanCurrentIndex + 1].classList.contains('ghost-lair') + ) { + this.pacmanCurrentIndex += 1 + } + if (this.squares[this.pacmanCurrentIndex + 1] === this.squares[392]) { + this.pacmanCurrentIndex = 364 + } + } + + if ($event.keyCode === KEY_CODE.LEFT_ARROW) { + if ( + this.pacmanCurrentIndex % this.width !== 0 && + !this.squares[this.pacmanCurrentIndex - 1].classList.contains('wall') && + !this.squares[this.pacmanCurrentIndex - 1].classList.contains('ghost-lair') + ) { + this.pacmanCurrentIndex -= 1 + } + if (this.squares[this.pacmanCurrentIndex - 1] === this.squares[363]) { + this.pacmanCurrentIndex = 391 + } + } + + if ($event.keyCode === KEY_CODE.DOWN_ARROW) { + if ( + this.pacmanCurrentIndex + this.width < this.width * this.width && + !this.squares[this.pacmanCurrentIndex + this.width].classList.contains('wall') && + !this.squares[this.pacmanCurrentIndex + this.width].classList.contains('ghost-lair') + ) { + this.pacmanCurrentIndex += this.width + } + } + + if ($event.keyCode === KEY_CODE.UP_ARROW) { + if ( + this.pacmanCurrentIndex - this.width >= 0 && + !this.squares[this.pacmanCurrentIndex - this.width].classList.contains('wall') && + !this.squares[this.pacmanCurrentIndex - this.width].classList.contains('ghost-lair') + ) { + this.pacmanCurrentIndex -= this.width + } + } + this.renderer.addClass(this.squares[this.pacmanCurrentIndex], 'pac-man'); + this.pacDotEaten(); + this.powerPelletEaten(); + this.checkForGameOver(this.squares, this.pacmanCurrentIndex); + this.checkForWin(); + })); + + + /** + * Generate the 28x28 grid board + * Colour all the element with the array define on the layout + */ + createBoard() { + for (let i = 0; i < this.layout.length; i++) { + const square = this.renderer.createElement('div'); + this.renderer.appendChild(this.grid.nativeElement, square); + this.squares.push(square) + let currentSquare = this.squares[i]; + + if (this.layout[i] === BOARD_CELLS.PAC_DOT) { + this.renderer.addClass(currentSquare, 'pac-dot'); + } else if (this.layout[i] === BOARD_CELLS.WALL) { + this.renderer.addClass(currentSquare, 'wall'); + } else if (this.layout[i] === BOARD_CELLS.GHOST_LAIR) { + this.renderer.addClass(currentSquare, 'ghost-lair'); + } else if (this.layout[i] === BOARD_CELLS.POWER_PALLET) { + this.renderer.addClass(currentSquare, 'power-pellet'); + } + } + } + + ngOnInit(): void { + } + + ngOnDestroy(): void { + } + + // init all the ghosts from the array + // set interval to move the ghost around. + initGhosts() { + this.ghosts.forEach(ghost => { + this.squares[ghost.currentIndex].classList.add('ghost'); + this.squares[ghost.currentIndex].classList.add(ghost.className); + this.moveGhost(ghost); + }); + + } + + /** + * Randomly move the ghost + * Check whether those ghost are afraid of the pacman + * Anti collision + * @param ghost + */ + moveGhost(ghost) { + let widthX = this.width; + let squaresY = this.squares; + let scoreValX = this.scoreValue; + let pacmanCurrentIndexX = this.pacmanCurrentIndex; + let checkForGameOverX = this.checkForGameOver; + ghost.timerId = setInterval(function () { + const directions = [-1, +1, +widthX, -widthX]; + let direction = directions[Math.floor(Math.random() * directions.length)]; + if (!squaresY[ghost.currentIndex + direction].classList.contains('ghost') && + !squaresY[ghost.currentIndex + direction].classList.contains('wall') + ) { + squaresY[ghost.currentIndex].classList.remove(ghost.className); + squaresY[ghost.currentIndex].classList.remove('ghost', 'scared-ghost'); + ghost.currentIndex += direction + ghost.previousIndex = ghost.currentIndex; + squaresY[ghost.currentIndex].classList.add(ghost.className, 'ghost'); + } else { + direction = directions[Math.floor(Math.random() * directions.length)]; + } + if (ghost.isScared) { + squaresY[ghost.currentIndex].classList.add('scared-ghost') + } + + if (ghost.isScared && squaresY[ghost.currentIndex].classList.contains('pac-man')) { + squaresY[ghost.currentIndex].classList.remove(ghost.className, 'ghost', 'scared-ghost') + ghost.currentIndex = ghost.startIndex + scoreValX += 100 + squaresY[ghost.currentIndex].classList.add(ghost.className, 'ghost') + } + checkForGameOverX(squaresY, pacmanCurrentIndexX); + }, ghost.speed); + } + + /** + * Check whether the game is over. if the pacman touches the ghost when it is not isScared + * then end the game by removing the keyUp event from the window/document. + */ + checkForGameOver(squaresX, pacmanCurrentIndexX) { + if (squaresX[pacmanCurrentIndexX].classList.contains('ghost') && + !squaresX[pacmanCurrentIndexX].classList.contains('scared-ghost') + ) { + this.ghosts.forEach(ghost => clearInterval(ghost.timerId)); + this.isPacmanAlive = false; + this.result.nativeElement.innerHTML = "Game Over!"; + } + + } + + + /** + * Check whether has pacman consume all the pellet from the grid + * if yes then display the winning message + */ + checkForWin() { + if (this.scoreValue === 274) { + this.ghosts.forEach(ghost => clearInterval(ghost.timerId)); + this.isPacmanAlive = false; + this.result.nativeElement.innerHTML = "You have Won!"; + } + } + + /** + * Initialize the board by generating 28x28 grid + * Render the initial start position of the pacman + * Also render the all the ghosts within the array list + */ + ngAfterViewInit() { + // create the pac man board + this.createBoard(); + this.renderer.addClass(this.squares[this.pacmanCurrentIndex], 'pac-man'); + this.initGhosts(); + } + + /** + * What happens when pacman is busy eating the small pellet + * Increment the score + * Remove the small pellet from the grid once is consume by pacman + */ + pacDotEaten() { + if (this.squares[this.pacmanCurrentIndex].classList.contains('pac-dot')) { + this.scoreValue++ + this.score.nativeElement.innerHTML = this.scoreValue; + this.squares[this.pacmanCurrentIndex].classList.remove('pac-dot'); + } + } + + /** + * Once pacman consume the power pellet it gains super power + * All the ghost will be afraid of em. + * By right the ghost should run away from pacman as much as possible + * Set a timeout for the ghost to turn back to hunting mode rather than + * be scared of pacman. + */ + powerPelletEaten() { + if (this.squares[this.pacmanCurrentIndex].classList.contains('power-pellet')) { + this.scoreValue += 10 + this.score.nativeElement.innerHTML = this.scoreValue; + this.ghosts.forEach(ghost => { + ghost.isScared = true; + // turn the ghost colour to aquamarine. + this.squares[ghost.currentIndex].classList.add(ghost.className, 'ghost', 'scared-ghost'); + }); + setTimeout(() => { + this.ghosts.forEach(ghost => { + ghost.isScared = false + this.squares[ghost.currentIndex].classList.remove(ghost.className, 'ghost', 'scared-ghost') + ghost.currentIndex = ghost.startIndex + this.squares[ghost.currentIndex].classList.add(ghost.className, 'ghost') + }) + }, 10000) + this.squares[this.pacmanCurrentIndex].classList.remove('power-pellet'); + } + } +} + diff --git a/src/app/@ansyn/ansyn/modules/overlays/components/overlay-navigation-bar/overlay-navigation-bar.component.spec.ts b/src/app/@ansyn/ansyn/modules/overlays/components/overlay-navigation-bar/overlay-navigation-bar.component.spec.ts index cf2c64f8cd..390b283515 100644 --- a/src/app/@ansyn/ansyn/modules/overlays/components/overlay-navigation-bar/overlay-navigation-bar.component.spec.ts +++ b/src/app/@ansyn/ansyn/modules/overlays/components/overlay-navigation-bar/overlay-navigation-bar.component.spec.ts @@ -21,11 +21,15 @@ import { OverlayStatusReducer, } from '../../overlay-status/reducers/overlay-status.reducer'; import { of } from 'rxjs'; +import { KeysListenerService } from "../../../core/services/keys-listener.service"; +import { EventEmitter } from "@angular/core"; +import { Key } from "ts-keycode-enum"; -describe('OverlyaNavigationBarComponent', () => { +describe('OverlayNavigationBarComponent', () => { let component: OverlayNavigationBarComponent; let fixture: ComponentFixture; let store: Store; + let keyListenerService: KeysListenerService; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ @@ -37,6 +41,7 @@ describe('OverlyaNavigationBarComponent', () => { }), TranslateModule.forRoot()], providers: [ { + KeysListenerService, provide: StatusBarConfig, useValue: { toolTips: {} } } @@ -48,6 +53,7 @@ describe('OverlyaNavigationBarComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(OverlayNavigationBarComponent); component = fixture.componentInstance; + keyListenerService = new KeysListenerService(); fixture.detectChanges(); }); @@ -84,30 +90,22 @@ describe('OverlyaNavigationBarComponent', () => { }); }); - [{ k: 'ArrowRight', n: 'goNextActive', f: 'clickGoAdjacent' }, { - k: 'ArrowLeft', + [{ k: Key.RightArrow, n: 'goNextActive', f: 'clickGoAdjacent' }, { + k: Key.LeftArrow, n: 'goPrevActive', f: 'clickGoAdjacent' }].forEach(key => { it(`onkeyup should call ${ key.n } when key = "${ key.k }"`, () => { spyOn(component, <'clickGoAdjacent'>key.f); expect(component[key.n]).toEqual(false); - const $event = { - key: key.k, - currentTarget: { - document: { - activeElement: { - className: [] - } - } - } - }; - component.onkeydown($event); + + component.onKeyDownEventCheck(new KeyboardEvent('keydown', {'keyCode': key.k})); expect(component[key.n]).toEqual(true); - component.onkeyup($event); + component.onKeyUpEventCheck(new KeyboardEvent('keyup', {'keyCode': key.k})) expect(component[key.n]).toEqual(false); expect(component[key.f]).toHaveBeenCalled(); }); }); }); + diff --git a/src/app/@ansyn/ansyn/modules/overlays/components/overlay-navigation-bar/overlay-navigation-bar.component.ts b/src/app/@ansyn/ansyn/modules/overlays/components/overlay-navigation-bar/overlay-navigation-bar.component.ts index 9ec3926f0d..03ccfc9f9e 100644 --- a/src/app/@ansyn/ansyn/modules/overlays/components/overlay-navigation-bar/overlay-navigation-bar.component.ts +++ b/src/app/@ansyn/ansyn/modules/overlays/components/overlay-navigation-bar/overlay-navigation-bar.component.ts @@ -1,6 +1,8 @@ -import { Component, HostListener, Inject, OnDestroy, OnInit } from '@angular/core'; +import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; -import { IStatusBarState } from '../../../status-bar/reducers/status-bar.reducer'; +import { + IStatusBarState, +} from '../../../status-bar/reducers/status-bar.reducer'; import { ExpandAction, GoAdjacentOverlay } from '../../../status-bar/actions/status-bar.actions'; import { IStatusBarConfig } from '../../../status-bar/models/statusBar-config.model'; import { StatusBarConfig } from '../../../status-bar/models/statusBar.config'; @@ -12,6 +14,8 @@ import { selectDropsAscending, selectFilteredOverlays } from '../../reducers/ove import { combineLatest } from 'rxjs'; import { IOverlay, IOverlayDrop } from '../../models/overlay.model'; import { TranslateService } from '@ngx-translate/core'; +import { KeysListenerService } from "../../../core/services/keys-listener.service"; +import { Key } from "ts-keycode-enum"; @Component({ selector: 'ansyn-overlay-navigation-bar', @@ -27,6 +31,10 @@ export class OverlayNavigationBarComponent implements OnInit, OnDestroy { isLastOverlay: boolean; overlaysLength: number; + private _scannedAreaKeys = Key.Tilde; + private _overlayHackKeys = Key.E; + private _toggleDirectionKeys = Key.D; + @AutoSubscription hasOverlayDisplay$ = this.store.select(selectOverlayOfActiveMap).pipe( tap(overlay => this.hasOverlayDisplay = Boolean(overlay)) @@ -39,101 +47,76 @@ export class OverlayNavigationBarComponent implements OnInit, OnDestroy { this.store.select(selectFilteredOverlays) ]).pipe( filter(([activeMapOverlay, overlays, filtered]: [IOverlay, IOverlayDrop[], any[]]) => Boolean(activeMapOverlay) && Boolean(overlays.length)), - tap(([activeMapOverlay, overlays, filtered]: [IOverlay, IOverlayDrop[], IOverlay[]]) => { + tap(([activeMapOverlay, overlays, filtered]: [IOverlay, IOverlayDrop[], IOverlay[]]) => { this.overlaysLength = filtered.length; this.isFirstOverlay = activeMapOverlay.id === overlays[0].id; this.isLastOverlay = activeMapOverlay.id === overlays[overlays.length - 1].id; }) ); - private _scannedAreaKeys = '`~;'.split(''); - private _overlayHackKeys = 'Eeק'.split(''); - private _toggleDirectionKeys = 'Ddג'.split(''); - constructor( protected store: Store, + public keyListenerService: KeysListenerService, @Inject(StatusBarConfig) public statusBarConfig: IStatusBarConfig, protected translateService: TranslateService ) { } - isElementNotValid($event: KeyboardEvent) { - const { activeElement } = ($event.currentTarget).document; - return this.isElementInput(activeElement) || this.isTimePicker(activeElement); - } + @AutoSubscription + onKeyDown$ = () => this.keyListenerService.keydown.pipe( + tap(($event: KeyboardEvent) => { + this.onKeyDownEventCheck($event); + }) + ); - isElementInput(activeElement) { - return activeElement instanceof HTMLInputElement; - } + onKeyDownEventCheck($event: KeyboardEvent) { + if (this.keyListenerService.keyWasUsed($event, Key.RightArrow)) { + this.goNextActive = true; + } else if (this.keyListenerService.keyWasUsed($event, Key.LeftArrow)) { + this.goPrevActive = true; + } - isTimePicker(activeElement) { - const { className } = activeElement; - return className.includes('owl') || className.includes('title'); + if (this.keyListenerService.keyWasUsed($event, this._overlayHackKeys)) { + this.store.dispatch(new EnableCopyOriginalOverlayDataAction(true)); + } } - @HostListener('window:keyup', ['$event']) - onkeyup($event: KeyboardEvent) { - if (this.isElementNotValid($event)) { - return; - } + @AutoSubscription + onKeyUp$ = () => this.keyListenerService.keyup.pipe( + tap(($event: KeyboardEvent) => { + this.onKeyUpEventCheck($event); + }) + ); - if (this.keyWasUsed($event, 'ArrowRight', 39)) { + onKeyUpEventCheck($event: KeyboardEvent) { + if (this.keyListenerService.keyWasUsed($event, Key.RightArrow)) { this.clickGoAdjacent(true); this.goNextActive = false; - } else if (this.keyWasUsed($event, 'ArrowLeft', 37)) { + } else if (this.keyListenerService.keyWasUsed($event, Key.LeftArrow)) { this.clickGoAdjacent(false); this.goPrevActive = false; } - if (this.keysWereUsed($event, this._overlayHackKeys)) { + if (this.keyListenerService.keyWasUsed($event, this._overlayHackKeys)) { this.store.dispatch(new EnableCopyOriginalOverlayDataAction(false)); } - if (this.keysWereUsed($event, this._toggleDirectionKeys)) { + if (this.keyListenerService.keyWasUsed($event, this._toggleDirectionKeys)) { const direction = this.translateService.instant('direction'); this.translateService.set('direction', direction === 'rtl' ? 'ltr' : 'rtl', 'default'); } } - - @HostListener('window:keydown', ['$event']) - onkeydown($event: KeyboardEvent) { - if (this.isElementNotValid($event)) { - return; - } - - if (this.keyWasUsed($event, 'ArrowRight', 39)) { - this.goNextActive = true; - } else if (this.keyWasUsed($event, 'ArrowLeft', 37)) { - this.goPrevActive = true; - } - - if (this.keysWereUsed($event, this._overlayHackKeys)) { - this.store.dispatch(new EnableCopyOriginalOverlayDataAction(true)); - } - } - - @HostListener('window:keypress', ['$event']) - onkeypress($event: KeyboardEvent) { - if (this.isElementNotValid($event)) { - return; - } - - if (this.keysWereUsed($event, this._scannedAreaKeys)) { - this.clickScannedArea(); - } - } - - private keyWasUsed(event: KeyboardEvent, key: string, keycode: number = key.charCodeAt(0)): boolean { - return event.key === key || event.which === keycode; // tslint:disable-line - // We need to check also on the old field event.which, for Chrome 44 - } - - private keysWereUsed(event: KeyboardEvent, keys: string[]): boolean { - return keys.some(key => this.keyWasUsed(event, key)); - } + @AutoSubscription + onkeypress$ = () => this.keyListenerService.keyup.pipe( + tap($event => { + if (this.keyListenerService.keyWasUsed($event, this._scannedAreaKeys)) { + this.clickScannedArea(); + } + } + )); clickGoAdjacent(isNext): void { - this.store.dispatch(new GoAdjacentOverlay({ isNext })); + this.store.dispatch(new GoAdjacentOverlay({isNext})); } clickScannedArea(): void { diff --git a/src/app/@ansyn/ansyn/modules/status-bar/components/tools/tools.module.ts b/src/app/@ansyn/ansyn/modules/status-bar/components/tools/tools.module.ts index 7e43ee89d9..1af11b5f71 100644 --- a/src/app/@ansyn/ansyn/modules/status-bar/components/tools/tools.module.ts +++ b/src/app/@ansyn/ansyn/modules/status-bar/components/tools/tools.module.ts @@ -16,6 +16,7 @@ import { MatButtonModule } from '@angular/material/button'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatSelectModule } from '@angular/material/select'; import { CoreModule } from '../../../core/core.module'; +import { PacmanPopupComponent } from '../../../easter-eggs/pacman-popup/pacman-popup.component'; // @dynamic @NgModule({ @@ -39,7 +40,7 @@ import { CoreModule } from '../../../core/core.module'; MatSelectModule ], providers: [ProjectionConverterService], - declarations: [ToolsComponent, AnnotationsControlComponent, MeasureControlComponent, ExportMapsPopupComponent], + declarations: [ToolsComponent, AnnotationsControlComponent, MeasureControlComponent, ExportMapsPopupComponent, PacmanPopupComponent], entryComponents: [ToolsComponent, MeasureControlComponent, ExportMapsPopupComponent], exports: [ToolsComponent, MatDialogModule] }) diff --git a/src/app/@ansyn/ansyn/modules/status-bar/components/tools/tools/tools.component.ts b/src/app/@ansyn/ansyn/modules/status-bar/components/tools/tools/tools.component.ts index 3087590436..e1b68ad99d 100644 --- a/src/app/@ansyn/ansyn/modules/status-bar/components/tools/tools/tools.component.ts +++ b/src/app/@ansyn/ansyn/modules/status-bar/components/tools/tools/tools.component.ts @@ -1,4 +1,4 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit, HostListener } from '@angular/core'; import { ClearActiveInteractionsAction, SetSubMenu, @@ -6,10 +6,10 @@ import { StopMouseShadow, UpdateMeasureDataOptionsAction, UpdateToolsFlags } from '../actions/tools.actions'; -import { Store } from '@ngrx/store'; +import { select, Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { selectSubMenu, selectToolFlags } from '../reducers/tools.reducer'; -import { map, take, tap } from 'rxjs/operators'; +import { filter, map, take, tap, withLatestFrom } from 'rxjs/operators'; import { AutoSubscription, AutoSubscriptions } from 'auto-subscriptions'; import { MatDialog } from '@angular/material/dialog'; import { ExportMapsPopupComponent } from '../export-maps-popup/export-maps-popup.component'; @@ -17,6 +17,10 @@ import { SubMenuEnum, toolsFlags } from '../models/tools.model'; import { selectActiveAnnotationLayer } from '../../../../menu-items/layers-manager/reducers/layers.reducer'; import { ComponentVisibilityService } from '../../../../../app-providers/component-visibility.service'; import { ComponentVisibilityItems } from '../../../../../app-providers/component-mode'; +import { PacmanPopupComponent } from '../../../../easter-eggs/pacman-popup/pacman-popup.component'; +import { KeysListenerService } from '../../../../core/services/keys-listener.service'; +import { selectOverlaysWithMapIds } from '@ansyn/map-facade'; +import { Key } from 'ts-keycode-enum'; @Component({ selector: 'ansyn-tools', @@ -28,6 +32,26 @@ import { ComponentVisibilityItems } from '../../../../../app-providers/component destroy: 'ngOnDestroy' }) export class ToolsComponent implements OnInit, OnDestroy { + + get subMenuEnum() { + return SubMenuEnum; + } + + get isGeoOptionsDisabled() { + return !this.flags?.get(toolsFlags.geoRegisteredOptionsEnabled); + } + + get shadowMouseDisabled() { + return this.flags?.get(toolsFlags.shadowMouseDisabled); + } + + get onShadowMouse() { + return this.flags?.get(toolsFlags.shadowMouse); + } + + get onMeasureTool() { + return this.flags?.get(toolsFlags.isMeasureToolActive); + } // for component readonly isExportShow: boolean; readonly isGoToShow: boolean; @@ -39,6 +63,9 @@ export class ToolsComponent implements OnInit, OnDestroy { public displayModeOn = false; public flags: Map; toolTipDirection = 'bottom' + + private _pacmanKey = Key.P; + @AutoSubscription public flags$: Observable> = this.store$.select(selectToolFlags).pipe( tap((flags: Map) => this.flags = flags) @@ -55,29 +82,10 @@ export class ToolsComponent implements OnInit, OnDestroy { subMenu: SubMenuEnum; - get subMenuEnum() { - return SubMenuEnum; - } - - get isGeoOptionsDisabled() { - return !this.flags?.get(toolsFlags.geoRegisteredOptionsEnabled); - } - - get shadowMouseDisabled() { - return this.flags?.get(toolsFlags.shadowMouseDisabled); - } - - get onShadowMouse() { - return this.flags?.get(toolsFlags.shadowMouse); - } - - get onMeasureTool() { - return this.flags?.get(toolsFlags.isMeasureToolActive); - } - constructor( protected store$: Store, public dialog: MatDialog, + public keyListenerService: KeysListenerService, componentVisibilityService: ComponentVisibilityService ) { this.isExportShow = componentVisibilityService.get(ComponentVisibilityItems.EXPORT); @@ -87,6 +95,20 @@ export class ToolsComponent implements OnInit, OnDestroy { this.isShadowMouseShow = componentVisibilityService.get(ComponentVisibilityItems.SHADOW_MOUSE); } + @AutoSubscription + onKeyUp$ = () => this.keyListenerService.keyup.pipe( + withLatestFrom(this.store$.select(selectOverlaysWithMapIds)), + filter(([$event, overlayWithMapIds]: [KeyboardEvent, { overlay: any, mapId: string, isActive: boolean }[]]) => + this.keyListenerService.keyWasUsed($event, this._pacmanKey) && + // open pacman only when there are no overlays displayed and no dialog + !this.isDialogShowing && + !overlayWithMapIds.some(overlayAndMapId => Boolean(overlayAndMapId.overlay))), + tap($event => { + this.togglePacmanDialog (); + }) + ); + + ngOnInit() { } @@ -98,15 +120,15 @@ export class ToolsComponent implements OnInit, OnDestroy { const value = this.onShadowMouse; if (value) { - this.store$.dispatch(new StopMouseShadow({ fromUser: true })); + this.store$.dispatch(new StopMouseShadow({fromUser: true})); } else { - this.store$.dispatch(new StartMouseShadow({ fromUser: true })); + this.store$.dispatch(new StartMouseShadow({fromUser: true})); } } toggleMeasureDistanceTool() { const value = !this.onMeasureTool; - this.store$.dispatch(new ClearActiveInteractionsAction({ skipClearFor: [UpdateMeasureDataOptionsAction] })); + this.store$.dispatch(new ClearActiveInteractionsAction({skipClearFor: [UpdateMeasureDataOptionsAction]})); this.store$.dispatch(new UpdateToolsFlags([{key: toolsFlags.isMeasureToolActive, value}])); } @@ -130,9 +152,16 @@ export class ToolsComponent implements OnInit, OnDestroy { toggleExportMapsDialog() { if (!this.isDialogShowing) { - const dialogRef = this.dialog.open(ExportMapsPopupComponent, { panelClass: 'custom-dialog' }); + const dialogRef = this.dialog.open(ExportMapsPopupComponent, {panelClass: 'custom-dialog'}); dialogRef.afterClosed().pipe(take(1), tap(() => this.isDialogShowing = false)).subscribe(); this.isDialogShowing = !this.isDialogShowing; } } + + togglePacmanDialog() { + const dialogRef = this.dialog.open(PacmanPopupComponent, {panelClass: 'custom-dialog'}); + dialogRef.afterClosed().pipe(take(1), tap(() => this.isDialogShowing = false)).subscribe(); + this.isDialogShowing = !this.isDialogShowing; + } + } diff --git a/src/app/@ansyn/ansyn/modules/status-bar/reducers/status-bar.reducer.ts b/src/app/@ansyn/ansyn/modules/status-bar/reducers/status-bar.reducer.ts index 6e6e164fc8..8fdf849cf1 100644 --- a/src/app/@ansyn/ansyn/modules/status-bar/reducers/status-bar.reducer.ts +++ b/src/app/@ansyn/ansyn/modules/status-bar/reducers/status-bar.reducer.ts @@ -77,3 +77,4 @@ export const selectIsOpenedFromOutside = createSelector(statusBarStateSelector, export const selectMarkedSecondSearchSensors = createSelector(statusBarStateSelector, (statusBar: IStatusBarState) => statusBar?.markSecondSearchSensors); export const selectGeoFilterActive = createSelector(selectGeoFilterStatus, (geoFilterStatus: IGeoFilterStatus) => geoFilterStatus.active); export const selectGeoFilterType = createSelector(selectGeoFilterStatus, (geoFilterStatus: IGeoFilterStatus) => geoFilterStatus.type); +