177 lines
8.8 KiB
TypeScript
177 lines
8.8 KiB
TypeScript
import { animate, keyframes, style, transition, trigger } from '@angular/animations';
|
||
import { Component, ElementRef, OnDestroy, OnInit, Renderer2, ViewChild } from '@angular/core';
|
||
import { Subject, Subscription } from 'rxjs';
|
||
import { take, takeUntil } from 'rxjs/operators';
|
||
import { getAudioPath } from 'src/app/helper/tts.helper';
|
||
import { VoiceService } from "../../services/voice.service";
|
||
|
||
|
||
interface RuleItem {
|
||
text: string;
|
||
action?: () => void;
|
||
voice?: number;
|
||
screpa?: boolean;
|
||
hideWithoutVoice?: boolean;
|
||
}
|
||
|
||
@Component({
|
||
selector: 'app-onboarding',
|
||
templateUrl: './onboarding.component.html',
|
||
styleUrls: ['./onboarding.component.scss'],
|
||
animations: [
|
||
trigger('fadeInAnimation', [
|
||
transition('void => *', []), // when the item is created
|
||
transition('* => void', []), // when the item is removed
|
||
transition('* => *', [ // when the item is changed
|
||
animate(1200, keyframes([ // animate for 1200 ms
|
||
style ({ opacity: 0.0}),
|
||
style ({ opacity: 1.0 }),
|
||
])),
|
||
]),
|
||
])]
|
||
})
|
||
export class OnboardingComponent implements OnInit, OnDestroy {
|
||
@ViewChild('avoidPenaltyCard') private avoidPenaltyCardEl: ElementRef;
|
||
@ViewChild('stolePrizeCard') private stolePrizeCardEl: ElementRef;
|
||
@ViewChild('versusCard') private versusCardEl: ElementRef;
|
||
@ViewChild('luckyCard') private luckyCardEl: ElementRef;
|
||
@ViewChild('banPlayerCard') private banPlayerEl: ElementRef;
|
||
@ViewChild('doubleTreasureCard') private doubleTreasureCardEl: ElementRef;
|
||
private rules: RuleItem[] = [
|
||
{ text: 'Игра состоит из вопросов с четырьмя вариантами ответов, правильный - только один.'},
|
||
{ text: 'Вопросы и ответы будут отображаться на экране и в Боте Благодарения.' },
|
||
{ text: 'Каждый игрок в начале игры имеет на руках 4 карты, набор карт определяется случайно. Описание карт ты найдешь ниже. После использования карты ты получаешь новую случайную карту.' },
|
||
{ text: 'На разыгрывание карты время ограничено, примерно 10 секунд.' },
|
||
{ text: 'Вы долго просили оптимизировать геймплей для медленных и глупых, и мы это сделали!'},
|
||
{ text: 'Задача игрока - ответить правильно и быстрее других, ну или хотя бы просто правильно в течение 20 секунд' },
|
||
{ text: 'Первый игрок, ответивший правильно, получает два очка' },
|
||
{ text: 'Все остальные, ответившие правильно, получают одно очко'},
|
||
{ text: 'Иногда за неправильные ответы игроки будут получать наказания' },
|
||
{ text: 'Избежать наказания можно только с помощью соотвествуещей карты, данную карту ты можешь сыграть перед озвучиванием наказания', action: () => {
|
||
this.shakeCard(this.avoidPenaltyCardEl);
|
||
}},
|
||
{ text: 'Карту "украсть приз" ты можешь сыграть в момент, когда кто-то собирается получить награду, но до момента того, как ты узнаешь, что это именно за приз', action: () => {
|
||
this.shakeCard(this.stolePrizeCardEl);
|
||
}},
|
||
{ text: 'Карту "Поединок" ты можешь разыграть в любой момент, чтобы вызвать игрока на дуэль', action: () => {
|
||
this.shakeCard(this.versusCardEl);
|
||
}},
|
||
{ text: '"Лаки карту" ты сможешь сыграть после своего правильного ответа, она увеличит твои шансы на получение приза', action: () => {
|
||
this.shakeCard(this.luckyCardEl);
|
||
}},
|
||
{
|
||
text: 'Карту бана можно сыграть на любого игрока (даже себя), чтобы отправить его на пару ходов во Владикавказ. Игрок не сможет участвовать в случайном количестве раундов',
|
||
action: () => {
|
||
this.shakeCard(this.banPlayerEl);
|
||
}
|
||
},
|
||
{
|
||
text: 'Ну и самая редкая карта - карта удвоения приза, играй ее перед тем, как получить награду, и вместо одной награды ты получишь две!',
|
||
action: () => {
|
||
this.shakeCard(this.doubleTreasureCardEl);
|
||
}
|
||
},
|
||
{ text: 'Не торопись с ответами, игра идет до той поры, пока мы не разыграем все призы' },
|
||
{
|
||
text: 'Ах да, так как создатель игры, работает на Microsoft, мы добавили привычные вам баги, но все их оставим в секрете',
|
||
},
|
||
{
|
||
text: 'Кажется, правила закончились'
|
||
}
|
||
];
|
||
|
||
public currentRule: string;
|
||
private currentRulePosition = 0;
|
||
private allRulesAnnounced = false;
|
||
private destroyed$ = new Subject<void>();
|
||
private voiceSubscription: Subscription;
|
||
public screpaText = '';
|
||
public showScrepa = false;
|
||
constructor(private voiceService: VoiceService, private renderer: Renderer2) { }
|
||
|
||
ngOnInit(): void {
|
||
this.voiceService.playAudio(getAudioPath('Итак, друзья, перейдем к правилам'));
|
||
setTimeout(() => {
|
||
this.beginRuleSwitching();
|
||
}, 3500);
|
||
}
|
||
ngOnDestroy(): void {
|
||
this.destroyed$.complete();
|
||
this.voiceSubscription?.unsubscribe();
|
||
}
|
||
|
||
shakeCard(card: ElementRef) {
|
||
this.renderer.addClass(card.nativeElement, 'shake');
|
||
this.renderer.addClass(card.nativeElement, 'zoom-in');
|
||
if(!this.allRulesAnnounced) {
|
||
this.voiceService.audioEndedSubject
|
||
.pipe(takeUntil(this.destroyed$),take(1))
|
||
.subscribe(() => {
|
||
this.renderer.addClass(card.nativeElement, 'zoom-out');
|
||
setTimeout(() => {
|
||
this.renderer.removeClass(card.nativeElement, 'zoom-out');
|
||
this.renderer.removeClass(card.nativeElement, 'zoom-in');
|
||
}, 3000);
|
||
this.stopShaking(card);
|
||
})
|
||
} else {
|
||
setTimeout(() => {
|
||
this.renderer.removeClass(card.nativeElement, 'zoom-out');
|
||
this.renderer.removeClass(card.nativeElement, 'zoom-in');
|
||
}, 5000);
|
||
this.stopShaking(card);
|
||
}
|
||
|
||
}
|
||
stopShaking(card: ElementRef) {
|
||
this.renderer.removeClass(card.nativeElement, 'shake');
|
||
}
|
||
|
||
private handleRule(rule: RuleItem) {
|
||
if(this.currentRulePosition > this.rules.length) {
|
||
this.currentRulePosition = 0;
|
||
}
|
||
console.log(`handle rule ${this.currentRulePosition}`);
|
||
|
||
this.currentRule = rule.screpa ? this.currentRule : rule.text.replace(/(?<=\[)(.*?)(?=\])/, '').replace('[','').replace(']','')
|
||
if (!this.allRulesAnnounced) {
|
||
const voice = rule.voice ? rule.voice : 1;
|
||
this.showScrepa = !!rule.screpa;
|
||
this.screpaText = rule.text;
|
||
this.voiceService.playAudio(getAudioPath(rule.text, voice));
|
||
} else {
|
||
if(rule.hideWithoutVoice || rule.screpa) {
|
||
this.playNextRule();
|
||
}
|
||
}
|
||
if (rule.action) {
|
||
rule.action();
|
||
}
|
||
}
|
||
|
||
private playNextRule() {
|
||
this.currentRulePosition++;
|
||
if (this.currentRulePosition === this.rules.length && !this.allRulesAnnounced) {
|
||
this.allRulesAnnounced = true;
|
||
this.voiceService.playAudio(getAudioPath(`Это все правила, надеюсь, все понятно. А если нет - сейчас Кирилл и Оксана вам все пояснят,
|
||
ну и совсем для тупых - пустила по кругу правила на экране,
|
||
а если ты их не поймешь - то очень жаль тебя глупенького`));
|
||
this.voiceService.audioEndedSubject.pipe(takeUntil(this.destroyed$),take(1)).subscribe(() => {
|
||
setInterval(() => { this.playNextRule() }, 6000);
|
||
this.currentRulePosition = 0
|
||
});
|
||
} else {
|
||
this.handleRule(this.rules[this.currentRulePosition]);
|
||
}
|
||
}
|
||
|
||
private beginRuleSwitching() {
|
||
this.handleRule(this.rules[this.currentRulePosition]);
|
||
this.voiceSubscription = this.voiceService.audioEndedSubject.pipe(takeUntil(this.destroyed$)).subscribe(() => {
|
||
setTimeout(() => this.playNextRule(), 500);
|
||
console.log(`audioEnded`);
|
||
})
|
||
|
||
}
|
||
|
||
}
|