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 {
IgxButtonModule,
IgxDialogComponent,
IgxDialogModule,
IgxIconModule,
IgxProgressBarModule,
IgxRadioModule,
@ -43,6 +45,7 @@ import { FormsModule } from '@angular/forms';
IgxIconModule,
IgxRadioModule,
IgxProgressBarModule,
IgxDialogModule,
],
providers: [],
bootstrap: [AppComponent]

View File

@ -1,12 +1,43 @@
<div class="container">
<div class="conveyor" #conveyor>
<img src="/assets/images/conveyor-belt/conveyor-{{conveyorFrame + 1}}.png" class="image">
<img src="/assets/images/conveyor-belt/conveyor-end-{{conveyorFrame + 1}}.png" class="image">
<igx-linear-bar [max]="timeMax" [value]="time" class="timeline" [text]="getTimerText()"></igx-linear-bar>
<div class="points">Точки: {{points}}</div>
<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 class="bins" [style.left]="(146 * (trashOffset + 1) - 50) + '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>
<div class="bins" [style.left]="(binWidth * (trashOffset) + 300) + 'px'" #bins>
<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-plastic.png" class="image" #bin>
<img src="/assets/images/conveyor-belt/bin-other.png" class="image" #bin>
</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 {
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 {
position: absolute;
@ -10,6 +43,7 @@ img {
.bins {
position: relative;
display: flex;
gap: 1em;
.image {
max-height: unset;
}
@ -18,6 +52,29 @@ img {
.conveyor {
display: flex;
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 { NavigationStart, Router } from '@angular/router';
import { filter } from 'rxjs/operators';
import { Saved } from '../db.service';
import { Card, Saved } from '../db.service';
class Rect {
public top: number;
@ -115,8 +115,8 @@ class Trash {
this.rect.right += offsetX;
this.rect.bottom += offsetY;
this.element.style.top = y + 'px';
this.element.style.left = x + 'px';
this.element.style.top = Math.floor(y / 2) * 2 + 'px';
this.element.style.left = Math.floor(x / 2) * 2 + 'px';
return { x, y, bin };
}
@ -127,37 +127,74 @@ class Trash {
templateUrl: './minigame-conveyor-recycling.component.html',
styleUrls: ['./minigame-conveyor-recycling.component.scss']
})
export class MinigameConveyorRecyclingComponent implements AfterViewInit {
export class MinigameConveyorRecyclingComponent implements AfterViewInit, OnInit {
conveyorFrame = 1;
frameCounterID: number;
trashTrowerID: number;
trashSpeed = 1000;
trashTypes: string[] = ['plastic', 'metal', 'glass', 'paper', 'other'];
mainLoopID: number;
timeCounterID: number;
trashTypes: string[] = ['metal-plastic', 'glass', 'paper', 'other'];
nextId = 0;
ended = false;
wonCard: Card;
throwOutSounds: HTMLAudioElement[] = [];
backgroundMusic: HTMLAudioElement;
trashSpeed = 1000;
simulationSpeed = 10;
friction = 5;
gravitation = 9;
suspended = true;
trashOffset = 0;
@ViewChildren('conveyor') conveyorElement;
@ViewChildren('bin') binElements;
bins: Bin[];
conveyor: Conveyor;
trashes: Saved<Trash>[] = [];
binWidth = 146;
binWidth = 110;
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) => {
if (!this.suspended) {
const code = e.keyCode as number;
if (code === 65) this.trashOffset--;
if (code === 68) this.trashOffset++;
if (this.trashOffset < 0) this.trashOffset = this.bins.length - 1;
if (this.trashOffset >= this.bins.length) this.trashOffset = 0;
if (this.trashOffset < 0) this.trashOffset = 0;
if (this.trashOffset >= this.bins.length) this.trashOffset = this.bins.length - 1;
}
}
constructor(
@ -166,47 +203,53 @@ export class MinigameConveyorRecyclingComponent implements AfterViewInit {
private element: ElementRef,
) { }
ngAfterViewInit(): void {
this.router.events.pipe(
filter((e: any) => e instanceof NavigationStart),
// tslint:disable-next-line: deprecation
).subscribe((e: NavigationStart) => {
this.end();
});
showPoint(points: number, x: number, y: number): void {
const el = document.createElement('span');
el.style.fontWeight = '600';
el.style.fontSize = '1.5em';
el.style.position = 'fixed';
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 {
clearInterval(this.frameCounterID);
document.body.removeEventListener('keydown', this.keydownListener);
getTimerText(): string {
const time = this.timeMax - this.time;
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 {
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) {
spawnTrash(): void {
const trash = new Trash();
const element = document.createElement('img');
element.style.position = 'absolute';
element.style.pointerEvents = 'none';
element.style.userSelect = 'none';
trash.velX = 0;
trash.velY = 0;
@ -219,17 +262,16 @@ export class MinigameConveyorRecyclingComponent implements AfterViewInit {
element.decode().then(() => {
trash.element = element;
this.element.nativeElement.prepend(element);
this.element.nativeElement.append(element);
trash.rect = new Rect(0, 0, element.width, element.height);
this.trashes.push({ id: (this.nextId++).toString(), el: trash });
});
}
this.trashes.forEach((trash, i) => {
updateTrash(trash: Saved<Trash>, i: number): void {
const newPos = trash.el.update(
9 / (1000 / this.simulationSpeed),
10 / (1000 / this.simulationSpeed),
this.gravitation / (1000 / this.simulationSpeed),
this.friction / (1000 / this.simulationSpeed),
this.conveyor, ...this.bins
);
if (newPos.y > document.body.getBoundingClientRect().bottom) {
@ -240,13 +282,123 @@ export class MinigameConveyorRecyclingComponent implements AfterViewInit {
if (newPos.bin) {
trash.el.element.remove();
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.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 {

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;
}
.dialog {
.dialog, .igx-dialog__window {
background-color: #f8f8f855;
backdrop-filter: blur(10px);
box-shadow: #0004 3px 3px 5px;
}