Somewhat working Conveyor Bin

This commit is contained in:
Kaloian Venkov 2021-03-13 23:28:42 +02:00
parent c38fa0e43d
commit d500e054fd
43 changed files with 321 additions and 79 deletions

View File

@ -6,6 +6,8 @@ import { AppComponent } from './app.component';
import { import {
IgxButtonModule, IgxButtonModule,
IgxDialogComponent,
IgxDialogModule,
IgxIconModule, IgxIconModule,
IgxProgressBarModule, IgxProgressBarModule,
IgxRadioModule, IgxRadioModule,
@ -43,6 +45,7 @@ import { FormsModule } from '@angular/forms';
IgxIconModule, IgxIconModule,
IgxRadioModule, IgxRadioModule,
IgxProgressBarModule, IgxProgressBarModule,
IgxDialogModule,
], ],
providers: [], providers: [],
bootstrap: [AppComponent] bootstrap: [AppComponent]

View File

@ -1,12 +1,43 @@
<div class="container"> <div class="container">
<div class="conveyor" #conveyor> <igx-linear-bar [max]="timeMax" [value]="time" class="timeline" [text]="getTimerText()"></igx-linear-bar>
<img src="/assets/images/conveyor-belt/conveyor-{{conveyorFrame + 1}}.png" class="image"> <div class="points">Точки: {{points}}</div>
<img src="/assets/images/conveyor-belt/conveyor-end-{{conveyorFrame + 1}}.png" class="image"> <div class="conveyor">
<img src="/assets/images/conveyor-belt/conveyor.gif">
<img src="/assets/images/conveyor-belt/conveyor.gif">
<img src="/assets/images/conveyor-belt/conveyor.gif">
<img src="/assets/images/conveyor-belt/conveyor.gif">
<img src="/assets/images/conveyor-belt/conveyor-end.gif">
<div class="conveyor-hitbox" #conveyor></div>
</div> </div>
<div class="bins" [style.left]="(146 * (trashOffset + 1) - 50) + 'px'" #bins> <div class="bins" [style.left]="(binWidth * (trashOffset) + 300) + 'px'" #bins>
<img src="/assets/images/conveyor-belt/bin-plastic.png" class="image" #bin>
<img src="/assets/images/conveyor-belt/bin-other.png" class="image" #bin>
<img src="/assets/images/conveyor-belt/bin-paper.png" class="image" #bin> <img src="/assets/images/conveyor-belt/bin-paper.png" class="image" #bin>
<img src="/assets/images/conveyor-belt/bin-glass.png" class="image" #bin> <img src="/assets/images/conveyor-belt/bin-glass.png" class="image" #bin>
<img src="/assets/images/conveyor-belt/bin-plastic.png" class="image" #bin>
<img src="/assets/images/conveyor-belt/bin-other.png" class="image" #bin>
</div> </div>
</div> </div>
<igx-dialog #startDialog [isOpen]="true" class="dialog" [closeOnEscape]="false">
<div class="dialog-local" style="">
<h4>ConveyorBin</h4>
<p>
В тази миниигра, вие трябва да сортирате боклуците по съответните кофи:
хартия - синя, стъкло - зелена, пластмаса и метал - жълта и битови отпадъци - червена.
За всеки боклук, чиято кофа уцелите печелите точка, а за всеки боклук, чиято кофа не уцелите - губите точка.
Имате около една минута да сортирате всички боклуци, но ако точките ви паднат под -10, губите.
</p>
<button igxButton igxRipple routerLink="">Върни се в началния екран</button>
<button igxButton igxRipple (click)="start(); startDialog.close()">Играй</button>
</div>
</igx-dialog>
<igx-dialog #endDialog>
<div class="dialog">
<h4>Завършихте минииграта</h4>
<h6>Събрани точки: {{points}}</h6>
<div class="wonCard" style="display: flex; flex-direction: column; align-items: center;" *ngIf="wonCard">
<h4>Вие спечелихте тази карта:</h4>
<app-card [card]="wonCard" [mini]="true" style="margin: auto; display: block;"></app-card>
</div>
<button igxButton igxRipple routerLink="">Върни се в началния екран</button>
<button igxButton igxRipple (click)="restart()">Играй отново</button>
</div>
</igx-dialog>

View File

@ -1,5 +1,38 @@
img { img {
image-rendering: crisp-edges; image-rendering: crisp-edges;
pointer-events: none;
user-select: none;
}
.dialog-local {
overflow: hidden;
width: 600px;
padding: 2em;
box-sizing: border-box;
border-radius: .5em;
button {
margin: 0 auto;
display: block;
}
h4 {
text-align: center;
margin: 0 1em;
}
h6 {
text-align: center;
margin: 0 2em;
margin-bottom: .5em;
}
.separator {
height: .5em;
width: 100%;
}
p {
text-align: center;
}
igx-radio {
display: block;
margin: .25em;
}
} }
.container { .container {
position: absolute; position: absolute;
@ -10,6 +43,7 @@ img {
.bins { .bins {
position: relative; position: relative;
display: flex; display: flex;
gap: 1em;
.image { .image {
max-height: unset; max-height: unset;
} }
@ -18,6 +52,29 @@ img {
.conveyor { .conveyor {
display: flex; display: flex;
width: max-content; width: max-content;
position: relative;
.conveyor-hitbox {
width: 100%;
height: 24px;
position: absolute;
bottom: 0;
left: 0;
}
}
.points {
position: fixed;
top: 0;
right: 0;
padding: 1em;
font-weight: 600;
z-index: 100;
}
.timeline {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 100;
} }
} }

View File

@ -1,7 +1,7 @@
import { AfterViewInit, Component, ElementRef, EventEmitter, Input, NgZone, OnInit, ViewChild, ViewChildren } from '@angular/core'; import { AfterViewInit, Component, ElementRef, EventEmitter, Input, NgZone, OnInit, ViewChild, ViewChildren } from '@angular/core';
import { NavigationStart, Router } from '@angular/router'; import { NavigationStart, Router } from '@angular/router';
import { filter } from 'rxjs/operators'; import { filter } from 'rxjs/operators';
import { Saved } from '../db.service'; import { Card, Saved } from '../db.service';
class Rect { class Rect {
public top: number; public top: number;
@ -115,8 +115,8 @@ class Trash {
this.rect.right += offsetX; this.rect.right += offsetX;
this.rect.bottom += offsetY; this.rect.bottom += offsetY;
this.element.style.top = y + 'px'; this.element.style.top = Math.floor(y / 2) * 2 + 'px';
this.element.style.left = x + 'px'; this.element.style.left = Math.floor(x / 2) * 2 + 'px';
return { x, y, bin }; return { x, y, bin };
} }
@ -127,37 +127,74 @@ class Trash {
templateUrl: './minigame-conveyor-recycling.component.html', templateUrl: './minigame-conveyor-recycling.component.html',
styleUrls: ['./minigame-conveyor-recycling.component.scss'] styleUrls: ['./minigame-conveyor-recycling.component.scss']
}) })
export class MinigameConveyorRecyclingComponent implements AfterViewInit { export class MinigameConveyorRecyclingComponent implements AfterViewInit, OnInit {
conveyorFrame = 1; conveyorFrame = 1;
frameCounterID: number; frameCounterID: number;
trashTrowerID: number; mainLoopID: number;
trashSpeed = 1000; timeCounterID: number;
trashTypes: string[] = ['plastic', 'metal', 'glass', 'paper', 'other']; trashTypes: string[] = ['metal-plastic', 'glass', 'paper', 'other'];
nextId = 0; nextId = 0;
ended = false;
wonCard: Card;
throwOutSounds: HTMLAudioElement[] = [];
backgroundMusic: HTMLAudioElement;
trashSpeed = 1000;
simulationSpeed = 10; simulationSpeed = 10;
friction = 5;
gravitation = 9;
suspended = true;
trashOffset = 0; trashOffset = 0;
@ViewChildren('conveyor') conveyorElement;
@ViewChildren('bin') binElements;
bins: Bin[]; bins: Bin[];
conveyor: Conveyor; conveyor: Conveyor;
trashes: Saved<Trash>[] = []; trashes: Saved<Trash>[] = [];
binWidth = 110;
binWidth = 146;
milliseconds = 0; milliseconds = 0;
won = false;
timeMax = 1;
time = 0;
points = 0;
binsAcceptedTypes: string[][] = [
[ 'paper' ],
[ 'glass' ],
[ 'metal-plastic' ],
[ 'other' ],
];
@ViewChildren('conveyor') conveyorElement;
@ViewChildren('bin') binElements;
@ViewChild('endDialog') endDialog;
initSound(url: string): HTMLAudioElement {
const el = document.createElement('audio');
el.src = url;
return el;
}
clearSound(sound: HTMLAudioElement): void {
document.body.append(sound);
sound.remove();
}
keydownListener = (e) => { keydownListener = (e) => {
if (!this.suspended) {
const code = e.keyCode as number; const code = e.keyCode as number;
if (code === 65) this.trashOffset--; if (code === 65) this.trashOffset--;
if (code === 68) this.trashOffset++; if (code === 68) this.trashOffset++;
if (this.trashOffset < 0) this.trashOffset = this.bins.length - 1; if (this.trashOffset < 0) this.trashOffset = 0;
if (this.trashOffset >= this.bins.length) this.trashOffset = 0; if (this.trashOffset >= this.bins.length) this.trashOffset = this.bins.length - 1;
}
} }
constructor( constructor(
@ -166,47 +203,53 @@ export class MinigameConveyorRecyclingComponent implements AfterViewInit {
private element: ElementRef, private element: ElementRef,
) { } ) { }
ngAfterViewInit(): void { showPoint(points: number, x: number, y: number): void {
this.router.events.pipe( const el = document.createElement('span');
filter((e: any) => e instanceof NavigationStart), el.style.fontWeight = '600';
// tslint:disable-next-line: deprecation el.style.fontSize = '1.5em';
).subscribe((e: NavigationStart) => { el.style.position = 'fixed';
this.end(); el.style.top = y + 'px';
}); el.style.left = x + 'px';
if (points < 0) el.style.color = 'red';
if (points > 0) el.style.color = 'green';
this.start(); if (points > 0) el.innerText = '+';
el.innerText += points.toString();
this.element.nativeElement.append(el);
el.animate([
{
transform: 'translateY(0)',
opacity: 1,
},
{
transform: 'translateY(-100px)',
opacity: 0
}
], {
duration: 500,
}).onfinish = () => {
el.remove();
};
} }
end(): void { getTimerText(): string {
clearInterval(this.frameCounterID); const time = this.timeMax - this.time;
document.body.removeEventListener('keydown', this.keydownListener); const minutes = Math.floor(time / 60).toString();
let seconds = Math.floor(time % 60).toString();
if (seconds.length === 1) seconds = "0" + seconds;
return `Остават: ${minutes}:${seconds}`;
} }
start(): void { spawnTrash(): void {
document.body.addEventListener('keydown', this.keydownListener);
this.bins = this.binElements._results.map((v: ElementRef) => {
const el = v.nativeElement;
const bin = new Bin();
bin.element = el;
bin.acceptedTypes = ['paper'];
return bin;
});
this.conveyor = new Conveyor();
this.conveyor.element = this.conveyorElement.first.nativeElement;
this.conveyor.speed = 2;
this.frameCounterID = setInterval(() => {
this.zone.run(() => {
this.conveyorFrame = (++this.conveyorFrame % 3);
});
}, 1000) as any as number;
this.frameCounterID = setInterval(() => {
if (this.milliseconds % (1000 / (this.simulationSpeed)) === 0) {
const trash = new Trash(); const trash = new Trash();
const element = document.createElement('img'); const element = document.createElement('img');
element.style.position = 'absolute'; element.style.position = 'absolute';
element.style.pointerEvents = 'none';
element.style.userSelect = 'none';
trash.velX = 0; trash.velX = 0;
trash.velY = 0; trash.velY = 0;
@ -219,17 +262,16 @@ export class MinigameConveyorRecyclingComponent implements AfterViewInit {
element.decode().then(() => { element.decode().then(() => {
trash.element = element; trash.element = element;
this.element.nativeElement.prepend(element); this.element.nativeElement.append(element);
trash.rect = new Rect(0, 0, element.width, element.height); trash.rect = new Rect(0, 0, element.width, element.height);
this.trashes.push({ id: (this.nextId++).toString(), el: trash }); this.trashes.push({ id: (this.nextId++).toString(), el: trash });
}); });
} }
updateTrash(trash: Saved<Trash>, i: number): void {
this.trashes.forEach((trash, i) => {
const newPos = trash.el.update( const newPos = trash.el.update(
9 / (1000 / this.simulationSpeed), this.gravitation / (1000 / this.simulationSpeed),
10 / (1000 / this.simulationSpeed), this.friction / (1000 / this.simulationSpeed),
this.conveyor, ...this.bins this.conveyor, ...this.bins
); );
if (newPos.y > document.body.getBoundingClientRect().bottom) { if (newPos.y > document.body.getBoundingClientRect().bottom) {
@ -240,13 +282,123 @@ export class MinigameConveyorRecyclingComponent implements AfterViewInit {
if (newPos.bin) { if (newPos.bin) {
trash.el.element.remove(); trash.el.element.remove();
this.trashes.splice(i, 1); this.trashes.splice(i, 1);
this.throwOutSounds[Math.floor(Math.random() * 3)].play();
const pointX = (trash.el.rect.left + trash.el.rect.right) / 2;
const pointY = trash.el.rect.bottom;
if (newPos.bin.acceptedTypes.includes(trash.el.type)) {
this.points++;
this.showPoint(1, pointX, pointY);
} }
else {
this.points--;
this.showPoint(-1, pointX, pointY);
}
}
}
ngOnInit(): void {
this.throwOutSounds = [
this.initSound('/assets/sound/conveyor-bin/throw-out-1.mp3'),
this.initSound('/assets/sound/conveyor-bin/throw-out-2.mp3'),
this.initSound('/assets/sound/conveyor-bin/throw-out-3.mp3'),
];
this.backgroundMusic = document.createElement('audio');
this.backgroundMusic.src = '/assets/sound/music/conveyorbin.wav';
this.backgroundMusic.onloadedmetadata = () => {
// this.timeMax = this.backgroundMusic.duration;
};
}
ngAfterViewInit(): void {
this.router.events.pipe(
filter((e: any) => e instanceof NavigationStart),
// tslint:disable-next-line: deprecation
).subscribe((e: NavigationStart) => {
this.end();
}); });
this.milliseconds++; this.init();
}
init(): void {
this.backgroundMusic.loop = true;
document.body.addEventListener('keydown', this.keydownListener);
this.bins = this.binElements._results.map((v: ElementRef, i: number) => {
const el = v.nativeElement;
const bin = new Bin();
bin.element = el;
bin.acceptedTypes = this.binsAcceptedTypes[i];
return bin;
});
this.conveyor = new Conveyor();
this.conveyor.element = this.conveyorElement.first.nativeElement;
this.conveyor.speed = 2;
this.frameCounterID = setInterval(() => {
if (!this.suspended) {
this.zone.run(() => {
this.conveyorFrame = (++this.conveyorFrame % 3);
});
}
}, 1000) as any as number;
this.mainLoopID = setInterval(() => {
if (!this.suspended) {
if (this.milliseconds % (this.simulationSpeed * 100) === 0) {
this.spawnTrash();
}
this.trashes.forEach((trash, i) => {
this.updateTrash(trash, i);
});
this.milliseconds += this.simulationSpeed;
}
}, this.simulationSpeed) as any as number; }, this.simulationSpeed) as any as number;
this.timeCounterID = setInterval(() => {
if (!this.suspended) {
this.time += 0.25;
if (this.time > this.timeMax) {
this.stop();
this.ended = true;
}
}
}, 250) as any as number;
}
end(): void {
this.stop();
this.ended = true;
}
finalise(): void {
clearInterval(this.frameCounterID);
clearInterval(this.timeCounterID);
clearInterval(this.mainLoopID);
document.body.removeEventListener('keydown', this.keydownListener);
this.throwOutSounds.forEach(v => this.clearSound(v));
this.clearSound(this.backgroundMusic);
}
stop(): void {
this.suspended = true;
this.backgroundMusic.pause();
this.backgroundMusic.currentTime = 0;
}
start(): void {
this.backgroundMusic.play();
this.suspended = false;
// tslint:disable-next-line: no-conditional-assignment
}
restart(): void {
this.points = 0;
this.start();
} }
getRandomType(): string { getRandomType(): string {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 510 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 510 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 503 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 271 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 425 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 294 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 821 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 860 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 837 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 742 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 793 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 915 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 700 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 280 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -37,9 +37,8 @@ $my-color-palette: igx-palette($primary: $company-color,
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
.dialog { .dialog, .igx-dialog__window {
background-color: #f8f8f855; background-color: #f8f8f855;
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
box-shadow: #0004 3px 3px 5px; box-shadow: #0004 3px 3px 5px;
} }