Somewhat working Conveyor Bin
@ -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]
|
||||
|
@ -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>
|
||||
</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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
keydownListener = (e) => {
|
||||
const code = e.keyCode as number;
|
||||
if (code === 65) this.trashOffset--;
|
||||
if (code === 68) this.trashOffset++;
|
||||
won = false;
|
||||
|
||||
if (this.trashOffset < 0) this.trashOffset = this.bins.length - 1;
|
||||
if (this.trashOffset >= this.bins.length) this.trashOffset = 0;
|
||||
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 = 0;
|
||||
if (this.trashOffset >= this.bins.length) this.trashOffset = this.bins.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
@ -166,6 +203,116 @@ export class MinigameConveyorRecyclingComponent implements AfterViewInit {
|
||||
private element: ElementRef,
|
||||
) { }
|
||||
|
||||
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';
|
||||
|
||||
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();
|
||||
};
|
||||
}
|
||||
|
||||
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}`;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
trash.type = this.getRandomType();
|
||||
trash.imageUrl = `/assets/images/conveyor-belt/${trash.type}-${Math.floor(Math.random() * 3) + 1}.png`;
|
||||
|
||||
element.src = trash.imageUrl;
|
||||
|
||||
element.decode().then(() => {
|
||||
trash.element = 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 });
|
||||
});
|
||||
}
|
||||
updateTrash(trash: Saved<Trash>, i: number): void {
|
||||
const newPos = trash.el.update(
|
||||
this.gravitation / (1000 / this.simulationSpeed),
|
||||
this.friction / (1000 / this.simulationSpeed),
|
||||
this.conveyor, ...this.bins
|
||||
);
|
||||
if (newPos.y > document.body.getBoundingClientRect().bottom) {
|
||||
trash.el.element.remove();
|
||||
this.trashes.splice(i, 1);
|
||||
}
|
||||
|
||||
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),
|
||||
@ -174,22 +321,19 @@ export class MinigameConveyorRecyclingComponent implements AfterViewInit {
|
||||
this.end();
|
||||
});
|
||||
|
||||
this.start();
|
||||
this.init();
|
||||
}
|
||||
|
||||
end(): void {
|
||||
clearInterval(this.frameCounterID);
|
||||
document.body.removeEventListener('keydown', this.keydownListener);
|
||||
}
|
||||
init(): void {
|
||||
this.backgroundMusic.loop = true;
|
||||
|
||||
start(): void {
|
||||
document.body.addEventListener('keydown', this.keydownListener);
|
||||
|
||||
this.bins = this.binElements._results.map((v: ElementRef) => {
|
||||
this.bins = this.binElements._results.map((v: ElementRef, i: number) => {
|
||||
const el = v.nativeElement;
|
||||
const bin = new Bin();
|
||||
bin.element = el;
|
||||
bin.acceptedTypes = ['paper'];
|
||||
bin.acceptedTypes = this.binsAcceptedTypes[i];
|
||||
|
||||
return bin;
|
||||
});
|
||||
@ -198,55 +342,63 @@ export class MinigameConveyorRecyclingComponent implements AfterViewInit {
|
||||
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 element = document.createElement('img');
|
||||
element.style.position = 'absolute';
|
||||
|
||||
trash.velX = 0;
|
||||
trash.velY = 0;
|
||||
|
||||
trash.type = this.getRandomType();
|
||||
trash.imageUrl = `/assets/images/conveyor-belt/${trash.type}-${Math.floor(Math.random() * 3) + 1}.png`;
|
||||
|
||||
element.src = trash.imageUrl;
|
||||
|
||||
element.decode().then(() => {
|
||||
trash.element = element;
|
||||
|
||||
this.element.nativeElement.prepend(element);
|
||||
trash.rect = new Rect(0, 0, element.width, element.height);
|
||||
|
||||
this.trashes.push({ id: (this.nextId++).toString(), el: trash });
|
||||
if (!this.suspended) {
|
||||
this.zone.run(() => {
|
||||
this.conveyorFrame = (++this.conveyorFrame % 3);
|
||||
});
|
||||
}
|
||||
|
||||
this.trashes.forEach((trash, i) => {
|
||||
const newPos = trash.el.update(
|
||||
9 / (1000 / this.simulationSpeed),
|
||||
10 / (1000 / this.simulationSpeed),
|
||||
this.conveyor, ...this.bins
|
||||
);
|
||||
if (newPos.y > document.body.getBoundingClientRect().bottom) {
|
||||
trash.el.element.remove();
|
||||
this.trashes.splice(i, 1);
|
||||
}, 1000) as any as number;
|
||||
this.mainLoopID = setInterval(() => {
|
||||
if (!this.suspended) {
|
||||
if (this.milliseconds % (this.simulationSpeed * 100) === 0) {
|
||||
this.spawnTrash();
|
||||
}
|
||||
|
||||
if (newPos.bin) {
|
||||
trash.el.element.remove();
|
||||
this.trashes.splice(i, 1);
|
||||
}
|
||||
});
|
||||
this.trashes.forEach((trash, i) => {
|
||||
this.updateTrash(trash, i);
|
||||
});
|
||||
|
||||
this.milliseconds++;
|
||||
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 {
|
||||
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 513 B |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 509 B |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 510 B |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 510 B |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 502 B |
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 503 B |
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 271 B |
BIN
apollo-frontend/src/assets/images/conveyor-belt/conveyor-4.png
Normal file
After Width: | Height: | Size: 502 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 435 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 425 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 294 B |
After Width: | Height: | Size: 435 B |
BIN
apollo-frontend/src/assets/images/conveyor-belt/conveyor-end.gif
Normal file
After Width: | Height: | Size: 967 B |
Before Width: | Height: | Size: 821 B |
Before Width: | Height: | Size: 860 B |
Before Width: | Height: | Size: 837 B |
BIN
apollo-frontend/src/assets/images/conveyor-belt/conveyor.gif
Normal file
After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 742 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 385 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 423 B |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 379 B |
After Width: | Height: | Size: 290 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 793 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 915 B |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 700 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 449 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 472 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 280 B |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.3 KiB |
BIN
apollo-frontend/src/assets/sound/conveyor-bin/throw-out-1.mp3
Normal file
BIN
apollo-frontend/src/assets/sound/conveyor-bin/throw-out-2.mp3
Normal file
BIN
apollo-frontend/src/assets/sound/conveyor-bin/throw-out-3.mp3
Normal 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;
|
||||
|
||||
}
|