UNPKG

ngx-snake

Version:

Snake game as an angular component, its only the core of the game... you need to add everything around it (controls, score...) yourself :)

368 lines (358 loc) 16 kB
import * as i0 from '@angular/core'; import { Injectable, Component, ViewEncapsulation, ChangeDetectionStrategy, Input, Output, NgModule } from '@angular/core'; import { ReplaySubject, Subject, BehaviorSubject, interval } from 'rxjs'; import { switchMap, filter, tap, take } from 'rxjs/operators'; import * as i2 from '@angular/common'; import { CommonModule } from '@angular/common'; var GameState; (function (GameState) { GameState[GameState["Paused"] = 0] = "Paused"; GameState[GameState["Started"] = 1] = "Started"; GameState[GameState["Over"] = 2] = "Over"; })(GameState || (GameState = {})); var TileState; (function (TileState) { TileState["Free"] = "free"; TileState["Head"] = "head"; TileState["Body"] = "body"; TileState["Tail"] = "tail"; TileState["Wall"] = "wall"; TileState["Food"] = "food"; })(TileState || (TileState = {})); var MoveDirections; (function (MoveDirections) { MoveDirections[MoveDirections["UP"] = 0] = "UP"; MoveDirections[MoveDirections["RIGHT"] = 1] = "RIGHT"; MoveDirections[MoveDirections["DOWN"] = 2] = "DOWN"; MoveDirections[MoveDirections["LEFT"] = 3] = "LEFT"; })(MoveDirections || (MoveDirections = {})); const INITIAL_SPEED = 700; class GameManagerService { constructor() { this._grid = []; this._grid$ = new ReplaySubject(1); this.grid$ = this._grid$.asObservable(); this._gameOver$ = new Subject(); this.gameOver$ = this._gameOver$; this._foodEaten$ = new Subject(); this.foodEaten$ = this._foodEaten$; this._gridSize = { h: 10, w: 10 }; this._snake = []; this._nextMoveDir = MoveDirections.RIGHT; this._moveDir = MoveDirections.RIGHT; this._food = null; this._interval$ = new BehaviorSubject(INITIAL_SPEED); this._paused = true; this._playable = true; this.signal$ = this._interval$ .asObservable() .pipe(switchMap((period) => interval(period)), filter(() => !this._paused), tap(() => this._gameCycle())); } initialize(height, width) { this._gridSize.w = width; this._gridSize.h = height; this._buildEmptyGrid(); this._initSnake(); this._drawSnake(); this._gridChanged(); this._signalSub = this.signal$.subscribe(); } ngOnDestroy() { if (this._signalSub) { this._signalSub.unsubscribe(); } } start() { if (this._playable) { this._paused = false; } } changeSpeed(period) { this._interval$.next(period); } pause() { this._paused = true; } reset() { this.pause(); this._playable = true; this._moveDir = MoveDirections.RIGHT; this._nextMoveDir = this._moveDir; this._interval$.next(INITIAL_SPEED); this._buildEmptyGrid(); this._initSnake(); this._drawSnake(); this._gridChanged(); } up() { this._moveDir !== MoveDirections.DOWN ? this._nextMoveDir = MoveDirections.UP : this._moveDir; } right() { this._moveDir !== MoveDirections.LEFT ? this._nextMoveDir = MoveDirections.RIGHT : this._moveDir; } down() { this._moveDir !== MoveDirections.UP ? this._nextMoveDir = MoveDirections.DOWN : this._moveDir; } left() { this._moveDir !== MoveDirections.RIGHT ? this._nextMoveDir = MoveDirections.LEFT : this._moveDir; } _endGame() { this._playable = false; this.pause(); this._gameOver$.next(); } _buildEmptyGrid() { const newGrid = []; for (let y = 0; y <= this._gridSize.h; y++) { const row = []; for (let x = 0; x <= this._gridSize.w; x++) { row.push(TileState.Free); } newGrid.push(row); } this._grid = newGrid; } _gridChanged() { this._grid$.next(this._grid); } _initSnake() { this._snake = []; const xCenter = Math.floor(this._gridSize.w / 2); const yCenter = Math.floor(this._gridSize.h / 2); this._snake.push({ x: xCenter, y: yCenter }); this._snake.push({ x: xCenter - 1, y: yCenter }); this._snake.push({ x: xCenter - 2, y: yCenter }); } _spawnFood() { if (!this._food) { const eligibleFields = []; for (let y = 1; y <= this._gridSize.h - 1; y++) { for (let x = 1; x <= this._gridSize.w - 1; x++) { if (this._grid[y][x] === TileState.Free) { eligibleFields.push({ x, y }); } } } const shuffled = eligibleFields.sort((a, b) => 0.5 - Math.random()); this._food = shuffled[0]; } this._grid[this._food.y][this._food.x] = TileState.Food; } _drawSnake() { const head = this._snake[0]; const tail = this._snake[this._snake.length - 1]; this._grid[head.y][head.x] = TileState.Head; for (let i = 1; i < this._snake.length - 1; i++) { const part = this._snake[i]; this._grid[part.y][part.x] = TileState.Body; } this._grid[tail.y][tail.x] = TileState.Tail; } _gameCycle() { const head = this._snake[0]; let newHead; this._moveDir = this._nextMoveDir; if (this._moveDir === MoveDirections.UP) { newHead = { x: head.x, y: head.y - 1 }; } else if (this._moveDir === MoveDirections.RIGHT) { newHead = { x: head.x + 1, y: head.y }; } else if (this._moveDir === MoveDirections.DOWN) { newHead = { x: head.x, y: head.y + 1 }; } else { // Moving left newHead = { x: head.x - 1, y: head.y }; } if (this._willCrash(newHead)) { return this._endGame(); } // position new head this._snake.unshift(newHead); if (this._willGrow(newHead)) { this._increaseSpeed(); this._foodEaten$.next(); this._food = null; } else { // drop old tail this._snake.pop(); } this._buildEmptyGrid(); this._drawSnake(); this._spawnFood(); this._gridChanged(); } /** * Checks if field is not currently occupied (is free to take) * @param newHead * @private */ _willCrash(newHead) { // Gets out of the board if (newHead.x < 0 || newHead.y < 0 || newHead.x > this._gridSize.w || newHead.y > this._gridSize.h) { return true; } const CRASHABLE_FIELDS = [ TileState.Body, TileState.Wall ]; if (CRASHABLE_FIELDS.includes(this._grid[newHead.y][newHead.x])) { return true; } // If crashing with tail then check if tail will move... if (this._grid[newHead.y][newHead.x] === TileState.Tail && this._willGrow(newHead)) { return true; } return false; } _willGrow(newHead) { if (this._food && this._food.y === newHead.y && this._food.x === newHead.x) { return true; } return false; } _increaseSpeed() { this._interval$ .pipe(take(1)) .subscribe((current) => { if (current >= 600) { this._interval$.next(current - 100); } else if (current >= 500) { this._interval$.next(current - 30); } else if (current >= 400) { this._interval$.next(current - 20); } else { this._interval$.next(current - 10); } }); } } GameManagerService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.1", ngImport: i0, type: GameManagerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); GameManagerService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.0.1", ngImport: i0, type: GameManagerService }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.1", ngImport: i0, type: GameManagerService, decorators: [{ type: Injectable }] }); class TileComponent { constructor(el, _renderer) { this.el = el; this._renderer = _renderer; } ngOnInit() { if (this.state) { this._renderer.addClass(this.el.nativeElement, this.state); } } } TileComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.1", ngImport: i0, type: TileComponent, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component }); TileComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.0.1", type: TileComponent, selector: "ngx-snake-tile", inputs: { state: "state" }, ngImport: i0, template: `<div></div>`, isInline: true, styles: ["ngx-snake-tile{display:block;background:#ccf1ed;border:1px solid #4e4645;width:25px;height:25px;float:left;margin:1px;box-sizing:border-box}ngx-snake-tile div{height:100%;width:100%;display:block;background:#ccf1ed}ngx-snake-tile.free div{background:#ccf1ed}ngx-snake-tile.head div{background:#4e6c31}ngx-snake-tile.body div{background:#4e6c31}ngx-snake-tile.tail div{background:#4e6c31}ngx-snake-tile.wall div{background:#C2C3C7}ngx-snake-tile.food{padding:3px}ngx-snake-tile.food div{background:#00E436}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.1", ngImport: i0, type: TileComponent, decorators: [{ type: Component, args: [{ selector: 'ngx-snake-tile', template: `<div></div>`, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["ngx-snake-tile{display:block;background:#ccf1ed;border:1px solid #4e4645;width:25px;height:25px;float:left;margin:1px;box-sizing:border-box}ngx-snake-tile div{height:100%;width:100%;display:block;background:#ccf1ed}ngx-snake-tile.free div{background:#ccf1ed}ngx-snake-tile.head div{background:#4e6c31}ngx-snake-tile.body div{background:#4e6c31}ngx-snake-tile.tail div{background:#4e6c31}ngx-snake-tile.wall div{background:#C2C3C7}ngx-snake-tile.food{padding:3px}ngx-snake-tile.food div{background:#00E436}\n"] }] }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.Renderer2 }]; }, propDecorators: { state: [{ type: Input }] } }); class BoardComponent { constructor() { } ngOnInit() { } } BoardComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.1", ngImport: i0, type: BoardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); BoardComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.0.1", type: BoardComponent, selector: "ngx-snake-board", inputs: { data: "data" }, ngImport: i0, template: "<div class=\"ngx-snake-board-row\" *ngFor=\"let row of data\">\n <ngx-snake-tile *ngFor=\"let tileState of row\"\n [state]=\"tileState\"></ngx-snake-tile>\n</div>\n", styles: [":host .ngx-snake-board-row{display:block;clear:both}\n"], components: [{ type: TileComponent, selector: "ngx-snake-tile", inputs: ["state"] }], directives: [{ type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.1", ngImport: i0, type: BoardComponent, decorators: [{ type: Component, args: [{ selector: 'ngx-snake-board', template: "<div class=\"ngx-snake-board-row\" *ngFor=\"let row of data\">\n <ngx-snake-tile *ngFor=\"let tileState of row\"\n [state]=\"tileState\"></ngx-snake-tile>\n</div>\n", styles: [":host .ngx-snake-board-row{display:block;clear:both}\n"] }] }], ctorParameters: function () { return []; }, propDecorators: { data: [{ type: Input }] } }); class NgxSnakeComponent { constructor(_manager) { this._manager = _manager; this.boardHeight = 10; this.boardWidth = 10; this.foodEaten = this._manager.foodEaten$; this.gameOver = this._manager.gameOver$; this.grid$ = this._manager.grid$; } ngOnInit() { this._manager.initialize(this.boardHeight, this.boardWidth); } actionUp() { this._manager.up(); } actionRight() { this._manager.right(); } actionDown() { this._manager.down(); } actionLeft() { this._manager.left(); } actionStart() { this._manager.start(); } actionStop() { this._manager.pause(); } actionReset() { this._manager.reset(); } } NgxSnakeComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.1", ngImport: i0, type: NgxSnakeComponent, deps: [{ token: GameManagerService }], target: i0.ɵɵFactoryTarget.Component }); NgxSnakeComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.0.1", type: NgxSnakeComponent, selector: "ngx-snake", inputs: { boardHeight: "boardHeight", boardWidth: "boardWidth" }, outputs: { foodEaten: "foodEaten", gameOver: "gameOver" }, providers: [GameManagerService], ngImport: i0, template: ` <ngx-snake-board [data]="grid$ | async"></ngx-snake-board> `, isInline: true, components: [{ type: BoardComponent, selector: "ngx-snake-board", inputs: ["data"] }], pipes: { "async": i2.AsyncPipe } }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.1", ngImport: i0, type: NgxSnakeComponent, decorators: [{ type: Component, args: [{ selector: 'ngx-snake', template: ` <ngx-snake-board [data]="grid$ | async"></ngx-snake-board> `, styles: [], providers: [GameManagerService] }] }], ctorParameters: function () { return [{ type: GameManagerService }]; }, propDecorators: { boardHeight: [{ type: Input }], boardWidth: [{ type: Input }], foodEaten: [{ type: Output }], gameOver: [{ type: Output }] } }); class NgxSnakeModule { static forRoot() { return { ngModule: NgxSnakeModule }; } } NgxSnakeModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.1", ngImport: i0, type: NgxSnakeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); NgxSnakeModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.0.1", ngImport: i0, type: NgxSnakeModule, declarations: [NgxSnakeComponent, BoardComponent, TileComponent], imports: [CommonModule], exports: [NgxSnakeComponent] }); NgxSnakeModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.0.1", ngImport: i0, type: NgxSnakeModule, imports: [[ CommonModule ]] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.1", ngImport: i0, type: NgxSnakeModule, decorators: [{ type: NgModule, args: [{ declarations: [ NgxSnakeComponent, BoardComponent, TileComponent ], imports: [ CommonModule ], exports: [ NgxSnakeComponent ] }] }] }); /* * Public API Surface of ngx-snake */ /** * Generated bundle index. Do not edit. */ export { NgxSnakeComponent, NgxSnakeModule }; //# sourceMappingURL=ngx-snake.mjs.map