+
+
+
+
+ *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%;
+}