diff --git a/src/engine/rules.ts b/src/engine/rules.ts index 17bf0ac..a1ec499 100644 --- a/src/engine/rules.ts +++ b/src/engine/rules.ts @@ -127,11 +127,12 @@ function tick1tryAndScore(game: Game, multiplier = 1) { game.state.tilesCleared += cleared.total; // compute score for clearing tiles + const fw = game.activeFramework(); const overflow = cleared.total - DC2MAX; const clearScore = game.state.level * game.state.conf.tileScoreMultiplier; const overflowBonus = clearScore * overflow; const fwBonus = cleared.breakdown.reduce((s: number, el) => { - if (el.fw === game.activeFramework()) { + if (el.fw === fw) { s += el.total * 2; } return s; @@ -165,6 +166,7 @@ function tick1tryAndScore(game: Game, multiplier = 1) { cleared, clearScore, levelScore, + fw, fwBonus, overflowBonus, score: game.state.score, diff --git a/src/front-end/actions/game.actions.ts b/src/front-end/actions/game.actions.ts index 52de6bd..0a73176 100644 --- a/src/front-end/actions/game.actions.ts +++ b/src/front-end/actions/game.actions.ts @@ -106,6 +106,7 @@ export function updateScoreData(levelData: { fwBonus: number; overflowBonus: number; score: number; + fw: number; }) { return { type: UPDATE_SCORE_DATA, diff --git a/src/front-end/angular/components/board.component.ts b/src/front-end/angular/components/board.component.ts index 21a2275..a9cd8d5 100644 --- a/src/front-end/angular/components/board.component.ts +++ b/src/front-end/angular/components/board.component.ts @@ -10,10 +10,23 @@ import { board, emptyTile, flexGrowRow, tileByNumber } from '../../styles'; }, template: `

LEVEL {{level}}

-
-
+
+ + +
+ *ngFor="let tile of row; trackBy: trackRow;" [value]="tile"> +
`, @@ -22,7 +35,16 @@ export class Board { @Input() board: number[][]; @Input() level: number; @Input() width: number; + @Input() height: number; @Input() isPaused: boolean; + @Input() lastOverflowBonus: number = 0; + @Input() lastFwBonus: number = 0; + @Input() lastFwBonusFw: number = 0; + @Input() lastClearScore: number = 0; + @Input() lastLevelScore: number = 0; + @Input() lastScoreUpdate: number = 0; + @Input() firstAnimationBlock: number = -1; + @Input() scoreDuration: number = 2000; cols: number[][]; emptyTile: string = emptyTile; diff --git a/src/front-end/angular/components/index.ts b/src/front-end/angular/components/index.ts index 5458db3..f4468a9 100644 --- a/src/front-end/angular/components/index.ts +++ b/src/front-end/angular/components/index.ts @@ -5,3 +5,5 @@ export * from './next-pieces-block.component'; export * from './tile.component'; export * from './forms'; export * from './score.component'; +export * from './score-hint.component'; +export * from './score-hint-cloud.component'; diff --git a/src/front-end/angular/components/score-hint-cloud.component.ts b/src/front-end/angular/components/score-hint-cloud.component.ts new file mode 100644 index 0000000..569f49e --- /dev/null +++ b/src/front-end/angular/components/score-hint-cloud.component.ts @@ -0,0 +1,66 @@ +import { Component, Input, HostBinding } from '@angular/core'; + +function xFromIndex(width: number, index: number) { + return ; +} + + +@Component({ + selector: 'score-hint-cloud', + template: ` + + + + + + + + +`, +}) +export class ScoreHintCloud { + @Input() lastOverflowBonus: number = 0; + @Input() lastFwBonus: number = 0; + @Input() lastFwBonusFw: number = 0; + @Input() lastClearScore: number = 0; + @Input() lastLevelScore: number = 0; + @Input() lastScoreUpdate: number = 0; + @Input() scoreDuration: number = 2000; + @Input() firstAnimationBlock: number = -1; + @Input() width: number = 0; + @Input() height: number = 0; + + get position() { + const x = 50; + const y = 50; + + return { xPercent: x , yPercent: y }; + } +} diff --git a/src/front-end/angular/components/score-hint.component.ts b/src/front-end/angular/components/score-hint.component.ts new file mode 100644 index 0000000..c5f869f --- /dev/null +++ b/src/front-end/angular/components/score-hint.component.ts @@ -0,0 +1,64 @@ +import { Component, Input, HostBinding } from '@angular/core'; +import { DomSanitizer, SafeStyle } from '@angular/platform-browser'; +import { tileColorByNumber } from '../../styles'; + +const jitterScale = 10; +@Component({ + selector: 'score-hint', + host: { + class: 'dib absolute' + }, + template: ` +
+
+ {{ text }} + +{{ score }} +
+
+`, +}) +export class ScoreHint { + @Input() colour: 10 | 20 | 30 | -10; + @Input() text: string = ''; + @Input() score: number; + @Input() duration: number; + @Input() startTime: number; + @Input() delay: number; + @Input() position: { xPercent: number, yPercent: number }; + @HostBinding('style') hostStyle: SafeStyle = ''; + jitterX: number; + jitterY: number; + + ngOnInit() { + this.jitterX = Math.floor(Math.random() * jitterScale) - jitterScale/2; + this.jitterY = Math.floor(Math.random() * jitterScale) - jitterScale/2; + } + + ngOnChanges() { + /* eh... */ + this.hostStyle = this.sanitizer.bypassSecurityTrustStyle( + `left: ${this.position.xPercent +this.jitterX}%; + bottom: ${this.position.yPercent + this.jitterY}%;`); + } + + canShow() { + if (this.score <= 0) { + return false; + } + if ((Date.now() - this.startTime > 0) && (Date.now() - this.startTime < this.duration)) { + return true; + } + return false; + } + + get getClass() { + return tileColorByNumber(this.colour) + ` animated animate-slow ${this.getDelayClass(this.delay)} fadeOutUp f1 `; + } + + getDelayClass(delay: number) { + if (delay < 1 && delay > 5 ) { return ''; } + return 'animate-delay-' + Math.floor(delay); + } + + constructor(private sanitizer: DomSanitizer) {} +} diff --git a/src/front-end/angular/components/tile.component.ts b/src/front-end/angular/components/tile.component.ts index c99594a..ede47f8 100644 --- a/src/front-end/angular/components/tile.component.ts +++ b/src/front-end/angular/components/tile.component.ts @@ -12,7 +12,9 @@ import { tileByNumber } from '../../styles'; width: 100%; } -
`, +
+ +
`, }) export class Tile { @Input() value: number; diff --git a/src/front-end/angular/containers/game.container.ts b/src/front-end/angular/containers/game.container.ts index 56654cb..a948af5 100644 --- a/src/front-end/angular/containers/game.container.ts +++ b/src/front-end/angular/containers/game.container.ts @@ -25,6 +25,15 @@ import { Resizer } from '../../aspect-resizer'; [board]="(board$ | async)" [level]="level$ | async" [width]="boardWidth$ | async" + [height]="boardHeight$ | async" + [lastOverflowBonus]="lastOverflowBonus$ | async" + [lastFwBonus]="lastFwBonus$ | async" + [lastFwBonusFw]="lastFwBonusFw$ | async" + [lastClearScore]="lastClearScore$ | async" + [lastLevelScore]="lastLevelScore$ | async" + [lastScoreUpdate]="lastScoreUpdate$ | async" + [scoreDuration]="scoreAnimationDelay$ | async" + [firstAnimationBlock]="firstAnimationBlock$ | async" >
@@ -65,7 +74,26 @@ export class Game implements AfterViewInit, OnInit, OnDestroy { score$; @select(s => s.game.level) level$; + @select(s => s.game.lastClearScore) + lastClearScore$; + @select(s => s.game.lastLevelScore) + lastLevelScore$; + @select(s => s.game.lastFwBonus) + lastFwBonus$; + @select(s => s.game.lastFwBonusFw) + lastFwBonusFw$; + @select(s => s.game.lastOverflowBonus) + lastOverflowBonus$; + @select(s => s.game.lastScoreUpdate) + lastScoreUpdate$; + @select(s => s.game.scoreAnimationDelay) + scoreAnimationDelay$; + @select(s => s.game.firstAnimationBlock) + firstAnimationBlock$; + @select(s => s.game.config.width) boardWidth$: number; + @select(s => s.game.config.height) + boardHeight$: number; deRegister: Function[] = []; pause: Function; done: Function; diff --git a/src/front-end/reducers/game.reducer.ts b/src/front-end/reducers/game.reducer.ts index 65de184..59d0994 100644 --- a/src/front-end/reducers/game.reducer.ts +++ b/src/front-end/reducers/game.reducer.ts @@ -1,4 +1,4 @@ -import { Block } from '../../interfaces'; +import { Block, TypedArray } from '../../interfaces'; import { GameConfigOptions } from '../../interfaces'; @@ -38,6 +38,7 @@ export interface IGameState { y: number; direction: 'row' | 'column'; }; + firstAnimationBlock: number; isPaused: boolean; isStopped: boolean; lastEvent: { keyCode: number }; @@ -45,8 +46,13 @@ export interface IGameState { levelProgress: number; preview: Block[]; score: number; + scoreAnimationDelay: number; + lastAnimationBlock: number; lastLevelScore: number; lastClearScore: number; + lastScoreUpdate: number; + lastFwBonus: number; + lastOverflowBonus: number; trimCols: number; trimRows: number; } @@ -58,19 +64,51 @@ const INIT: IGameState = deepFreeze({ debug: true, }), currentGameViewportDimensions: { x: 0, y: 0, direction: 'row' as 'row' }, + firstAnimationBlock: -1, isPaused: false, isStopped: false, + lastAnimationBlock: -1, lastEvent: { keyCode: 0 }, lastLevelScore: 0, lastClearScore: 0, + lastScoreUpdate: 0, + lastFwBonus: 0, + lastOverflowBonus: 0, level: 0, levelProgress: 0, preview: [], score: 0, + scoreAnimationDelay: 2000, trimCols: 2, trimRows: 0, }); +function getIndex(buffer: TypedArray, type: 'indexOf' | 'lastIndexOf') { + let index = buffer[type](11); + if (index < 0) { + index = buffer[type](21); + if (index < 0) { + index = buffer[type](31); + } + } + return index; +} + +function getAnimationBlocks(buffer: TypedArray) { + const indexStart = getIndex(buffer, 'indexOf'); + if (indexStart < 0) { + return { + firstAnimationBlock: -1, + lastAnimationBlock: -1, + }; + } + const indexEnd = getIndex(buffer, 'lastIndexOf'); + return { + firstAnimationBlock: indexStart, + lastAnimationBlock: indexEnd, + }; +} + export function game(state = INIT, { payload, type }) { const bMergeProp: (prop: string) => any = partial(mergeProp, state, payload); @@ -94,7 +132,10 @@ export function game(state = INIT, { payload, type }) { return bMergeProp('activePiece'); case UPDATE_BUFFER: - return bMergeProp('buffer'); + return { + ...bMergeProp('buffer'), + ...getAnimationBlocks(payload), + }; case UPDATE_PREVIEW: return bMergeProp('preview'); @@ -114,10 +155,12 @@ export function game(state = INIT, { payload, type }) { case UPDATE_SCORE_DATA: return { ...state, - lastLevelScore: payload.levelScore, - lastClearScore: payload.clearScore, - lastOverflowBonus: payload.overflowBonus, - lastFwBonus: payload.fwBonus, + lastLevelScore: payload.levelScore || 0, + lastClearScore: payload.clearScore || 0, + lastOverflowBonus: payload.overflowBonus || 0, + lastFwBonus: payload.fwBonus || 0, + lastFwBonusFw: payload.fw || 0, + lastScoreUpdate: Date.now(), }; default: diff --git a/src/front-end/styles.ts b/src/front-end/styles.ts index d8df37c..b1040ee 100644 --- a/src/front-end/styles.ts +++ b/src/front-end/styles.ts @@ -5,7 +5,7 @@ const colours = deepFreeze({ black: 'bd-black', blue: 'bd-blue', red: 'bd-red', - green: 'bd-blue', + green: 'bd-green', bgGreen: 'bd-bg-green', bgBlue: 'bd-bg-blue', bgRed: 'bd-bg-red', @@ -106,3 +106,21 @@ export function tileByNumber(val: number) { return tileDefault; } } +export function tileColorByNumber(val: number) { + switch (val) { + case 10: + case 11: + case 10 + SHADOW_OFFSET: + return 'vue-green'; + case 20: + case 21: + case 20 + SHADOW_OFFSET: + return 'angular-red'; + case 30: + case 31: + case 30 + SHADOW_OFFSET: + return `react-blue`; + default: + return 'white'; + } +} diff --git a/src/styles/animate.css b/src/styles/animate.css index 8022eda..3bf45a3 100644 --- a/src/styles/animate.css +++ b/src/styles/animate.css @@ -2,10 +2,47 @@ animation-duration: var(--animate-speed); animation-fill-mode: both; } +.animated.animate-slow { + animation-duration: calc(var(--animate-speed) * 2); +} .animated.infinite { animation-iteration-count: infinite; } +.animated.animate-delay-1 { + animation-delay: 100ms; +} +.animated.animate-delay-2 { + animation-delay: 200ms; +} +.animated.animate-delay-3 { + animation-delay: 300ms; +} +.animated.animate-delay-4 { + animation-delay: 400ms; +} +.animated.animate-delay-5 { + animation-delay: 500ms; +} + +.fadeOutUp { + animation-name: fadeOutUp; +} +@keyframes fadeOutUp { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } +} + +.bounceOut { + animation-name: bounceOut; +} @keyframes bounceOut { 20% { transform: scale3d(0.9, 0.9, 0.9); @@ -20,7 +57,3 @@ transform: scale3d(0.3, 0.3, 0.3); } } - -.bounceOut { - animation-name: bounceOut; -} diff --git a/src/styles/sizes.css b/src/styles/sizes.css index 4d41834..d2d0b40 100644 --- a/src/styles/sizes.css +++ b/src/styles/sizes.css @@ -116,3 +116,9 @@ .man2-ns { margin: -0.5rem; } +.mbn-50 { + margin-bottom: -50%; +} +.mln-50 { + margin-left: -50%; +}