From 2ec90e868c7eb8ee4732521563ec5945b8783e73 Mon Sep 17 00:00:00 2001 From: "Michael J. Bennett" Date: Sun, 15 Apr 2018 17:18:51 -0400 Subject: [PATCH 1/4] STASH --- src/engine/rules.ts | 4 +- src/front-end/actions/game.actions.ts | 1 + .../angular/components/board.component.ts | 29 +++++++- src/front-end/angular/components/index.ts | 2 + .../components/score-hint-cloud.component.ts | 74 +++++++++++++++++++ .../components/score-hint.component.ts | 51 +++++++++++++ .../angular/components/tile.component.ts | 4 +- .../angular/containers/game.container.ts | 28 +++++++ src/front-end/reducers/game.reducer.ts | 55 ++++++++++++-- src/styles/animate.css | 19 +++++ 10 files changed, 256 insertions(+), 11 deletions(-) create mode 100644 src/front-end/angular/components/score-hint-cloud.component.ts create mode 100644 src/front-end/angular/components/score-hint.component.ts 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..537631c 100644 --- a/src/front-end/angular/components/board.component.ts +++ b/src/front-end/angular/components/board.component.ts @@ -6,14 +6,28 @@ import { board, emptyTile, flexGrowRow, tileByNumber } from '../../styles'; selector: 'board', host: { class: - 'ba bw2 b--angular-red mr2 mr4-ns flex flex-column shadow-angular-red w-two-thirds', + 'ba bw2 b--angular-red mr2 mr4-ns flex flex-column shadow-angular-red w-two-thirds relative', }, template: `

LEVEL {{level}}

-
+ + +
+ *ngFor="let tile of row; trackBy: trackRow;" [value]="tile"> +
`, @@ -22,7 +36,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..2a135fd --- /dev/null +++ b/src/front-end/angular/components/score-hint-cloud.component.ts @@ -0,0 +1,74 @@ +import { Component, Input, HostBinding } from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; + +function xFromIndex(width: number, index: number) { + return index % width; +} + +function yFromIndex(width: number, index: number) { + return Math.floor(index / width); +} + +@Component({ + selector: 'score-hint-cloud', + host: { + class: '', + style: 'position: absolute;', + }, + 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; + @HostBinding('style') + get style() { + if (this.firstAnimationBlock <= 0) { + return ''; + } + const x = xFromIndex(this.width, this.firstAnimationBlock); + const y = yFromIndex(this.width, this.firstAnimationBlock); + const style = ` + position: absolute; + bottom: ${x / this.width * 100}%; + right: ${y / this.height * 100}% + z-index: 1000; + `; + + console.log('style', style); + + /** Hope this isn't a problem := */ + return this.sanitizer.bypassSecurityTrustStyle(style); + } + constructor(private sanitizer: DomSanitizer) {} +} 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..5b4a850 --- /dev/null +++ b/src/front-end/angular/components/score-hint.component.ts @@ -0,0 +1,51 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'score-hint', + host: { + class: '', + style: 'position absolute; top: 50%; left: 50%;', + }, + template: ` +
+{{ score }}
+`, +}) +export class ScoreHint { + @Input() colour: 10 | 20 | 30 | -10; + @Input() duration: number = 2000; + @Input() score: number; + @Input() startTime: number; + + canShow() { + if (this.score <= 0) { + return false; + } + if (Date.now() - this.startTime < this.duration) { + return true; + } + return false; + } + + colourFromInteger(value: number) { + if (value === -10) { + return 'brand-purple'; + } + if (value === 10) { + return 'vue-green'; + } + if (value === 20) { + return 'angular-red'; + } + if (value === 30) { + return 'react-blue'; + } + return 'brand-purple'; + } + + getClass() { + return this.colourFromInteger(this.colour) + ' animated fadeOut'; + } +} 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/styles/animate.css b/src/styles/animate.css index 8022eda..7d62ab6 100644 --- a/src/styles/animate.css +++ b/src/styles/animate.css @@ -6,6 +6,25 @@ animation-iteration-count: infinite; } +.fadeOut { + animation-name: fadeOut; +} + +@keyframes fadeOut { + 0% { + opacity: 0; + } + 10% { + opacity: 1; + } + 90% { + opacity: 1; + } + 100% { + opacity: 0; + } +} + @keyframes bounceOut { 20% { transform: scale3d(0.9, 0.9, 0.9); From 32f71d5e32239053da25747892945058ce97ff62 Mon Sep 17 00:00:00 2001 From: Oleg Rakitine Date: Sun, 15 Apr 2018 17:51:07 -0400 Subject: [PATCH 2/4] Fixed posiioning of hint cloud --- src/front-end/angular/components/board.component.ts | 4 ++-- .../angular/components/score-hint-cloud.component.ts | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/front-end/angular/components/board.component.ts b/src/front-end/angular/components/board.component.ts index 537631c..6fd5297 100644 --- a/src/front-end/angular/components/board.component.ts +++ b/src/front-end/angular/components/board.component.ts @@ -6,11 +6,11 @@ import { board, emptyTile, flexGrowRow, tileByNumber } from '../../styles'; selector: 'board', host: { class: - 'ba bw2 b--angular-red mr2 mr4-ns flex flex-column shadow-angular-red w-two-thirds relative', + 'ba bw2 b--angular-red mr2 mr4-ns flex flex-column shadow-angular-red w-two-thirds', }, template: `

LEVEL {{level}}

-
+
Date: Mon, 16 Apr 2018 00:34:44 -0400 Subject: [PATCH 3/4] Bonuses are animated --- .../angular/components/board.component.ts | 3 +- .../components/score-hint-cloud.component.ts | 68 ++++++++----------- .../components/score-hint.component.ts | 59 +++++++++------- src/front-end/styles.ts | 20 +++++- src/styles/animate.css | 30 ++++---- src/styles/sizes.css | 6 ++ 6 files changed, 102 insertions(+), 84 deletions(-) diff --git a/src/front-end/angular/components/board.component.ts b/src/front-end/angular/components/board.component.ts index 6fd5297..a9cd8d5 100644 --- a/src/front-end/angular/components/board.component.ts +++ b/src/front-end/angular/components/board.component.ts @@ -21,8 +21,7 @@ import { board, emptyTile, flexGrowRow, tileByNumber } from '../../styles'; [firstAnimationBlock]="firstAnimationBlock" [scoreDuration]="scoreDuration" [width]="width" - [height]="height" - > + [height]="height">
+ [colour]="-1"> + + [colour]="-1"> + + [colour]="-1"> + + [colour]="-1"> + `, }) export class ScoreHintCloud { @@ -50,24 +53,11 @@ export class ScoreHintCloud { @Input() firstAnimationBlock: number = -1; @Input() width: number = 0; @Input() height: number = 0; - @HostBinding('style') - get style() { - if (this.firstAnimationBlock <= 0) { - return ''; - } - const x = xFromIndex(this.width, this.firstAnimationBlock); - const y = yFromIndex(this.width, this.firstAnimationBlock); - const style = ` - position: absolute; - bottom: ${x / this.width * 100}%; - right: ${y / this.height * 100}% - z-index: 1000; - `; - console.log('style', style); + get position() { + const x = 50; + const y = 50; - /** Hope this isn't a problem := */ - return this.sanitizer.bypassSecurityTrustStyle(style); + return { xPercent: x , yPercent: y }; } - constructor(private sanitizer: DomSanitizer) {} } diff --git a/src/front-end/angular/components/score-hint.component.ts b/src/front-end/angular/components/score-hint.component.ts index 5b4a850..76222d3 100644 --- a/src/front-end/angular/components/score-hint.component.ts +++ b/src/front-end/angular/components/score-hint.component.ts @@ -1,51 +1,58 @@ -import { Component, Input } from '@angular/core'; +import { Component, Input, HostBinding } from '@angular/core'; +import { DomSanitizer, SafeStyle } from '@angular/platform-browser'; +import { tileColorByNumber } from '../../styles'; @Component({ selector: 'score-hint', host: { - class: '', - style: 'position absolute; top: 50%; left: 50%;', + class: 'dib absolute' }, template: ` -
+{{ score }}
+
+
+ {{ text }}
+ +{{ score }} +
+
`, }) export class ScoreHint { @Input() colour: 10 | 20 | 30 | -10; - @Input() duration: number = 2000; + @Input() text: string = ''; @Input() score: number; + @Input() duration: number; @Input() startTime: number; + @Input() position: { xPercent: number, yPercent: number }; + @HostBinding('style') hostStyle: SafeStyle = ''; + jitterX: number; + jitterY: number; + + ngOnInit() { + this.jitterX = Math.floor(Math.random() * 8) - 4; + this.jitterY = Math.floor(Math.random() * 8) - 4; + } + + 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 < this.duration) { + if ((Date.now() - this.startTime > 0) && (Date.now() - this.startTime < this.duration)) { + // console.log(this.text, this.score) return true; } return false; } - colourFromInteger(value: number) { - if (value === -10) { - return 'brand-purple'; - } - if (value === 10) { - return 'vue-green'; - } - if (value === 20) { - return 'angular-red'; - } - if (value === 30) { - return 'react-blue'; - } - return 'brand-purple'; + get getClass() { + return tileColorByNumber(this.colour) + ' animated animate-slow fadeOutUp f1'; } - getClass() { - return this.colourFromInteger(this.colour) + ' animated fadeOut'; - } + constructor(private sanitizer: DomSanitizer) {} } 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 7d62ab6..62027e7 100644 --- a/src/styles/animate.css +++ b/src/styles/animate.css @@ -2,29 +2,31 @@ animation-duration: var(--animate-speed); animation-fill-mode: both; } +.animate-slow { + animation-duration: calc(var(--animate-speed) * 2); +} .animated.infinite { animation-iteration-count: infinite; } -.fadeOut { - animation-name: fadeOut; +.fadeOutUp { + animation-name: fadeOutUp; } - -@keyframes fadeOut { - 0% { - opacity: 0; - } - 10% { +@keyframes fadeOutUp { + from { opacity: 1; } - 90% { - opacity: 1; - } - 100% { + + 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); @@ -39,7 +41,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%; +} From 8889b23a0c6a98f8ca4e1eab7aa2e61f928aba4f Mon Sep 17 00:00:00 2001 From: Oleg Rakitine Date: Mon, 16 Apr 2018 11:56:29 -0400 Subject: [PATCH 4/4] Optimised animation delays --- .../components/score-hint-cloud.component.ts | 11 ++++++---- .../components/score-hint.component.ts | 20 ++++++++++++------- src/styles/animate.css | 18 ++++++++++++++++- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/front-end/angular/components/score-hint-cloud.component.ts b/src/front-end/angular/components/score-hint-cloud.component.ts index 47de8cb..569f49e 100644 --- a/src/front-end/angular/components/score-hint-cloud.component.ts +++ b/src/front-end/angular/components/score-hint-cloud.component.ts @@ -10,7 +10,7 @@ function xFromIndex(width: number, index: number) { template: ` -
- {{ text }}
- +{{ score }} +
+ {{ text }} + +{{ score }}
`, @@ -22,14 +23,15 @@ export class ScoreHint { @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() * 8) - 4; - this.jitterY = Math.floor(Math.random() * 8) - 4; + this.jitterX = Math.floor(Math.random() * jitterScale) - jitterScale/2; + this.jitterY = Math.floor(Math.random() * jitterScale) - jitterScale/2; } ngOnChanges() { @@ -44,14 +46,18 @@ export class ScoreHint { return false; } if ((Date.now() - this.startTime > 0) && (Date.now() - this.startTime < this.duration)) { - // console.log(this.text, this.score) return true; } return false; } get getClass() { - return tileColorByNumber(this.colour) + ' animated animate-slow fadeOutUp f1'; + 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/styles/animate.css b/src/styles/animate.css index 62027e7..3bf45a3 100644 --- a/src/styles/animate.css +++ b/src/styles/animate.css @@ -2,12 +2,28 @@ animation-duration: var(--animate-speed); animation-fill-mode: both; } -.animate-slow { +.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;