versus implementation

This commit is contained in:
Kirill Ivlev 2024-11-13 02:09:03 +04:00
parent afabd52e02
commit 9462031af5
18 changed files with 227 additions and 39 deletions

View file

@ -1,6 +1,12 @@
<div class="game-testing m-2" *ngIf="!prodMode">
<h3>Game testing menu</h3>
<h4>Players</h4>
<button class="btn btn-danger" (click)="resetAllPlayersScore()">Reset score to 0</button>
<h4>Game</h4>
<button class="btn btn-danger" (click)="clearGameQueue()">Clear queue</button>
<h4>Versus</h4>
<button class="btn btn-danger" (click)="simulateVersus()">Begin versus</button>
<button class="btn btn-danger" (click)="resetAllVersusTasksAsIncompleted()">Mark all as uncompleted</button>
<button class="btn btn-danger" disabled>Stop versus</button>
<button class="btn btn-danger" disabled>Simulate endgame points</button>
</div>

View file

@ -37,4 +37,16 @@ export class AdminTestingComponent implements OnInit, OnDestroy {
simulateVersus() {
this.testingApiService.simulateVersus().pipe(takeUntil(this.destroyed$)).subscribe((r) => console.log(r));
}
resetAllVersusTasksAsIncompleted() {
this.testingApiService.resetAllVersusTasksAsIncompleted().pipe(takeUntil(this.destroyed$)).subscribe((r) => console.log(r));
}
resetAllPlayersScore() {
this.testingApiService.resetAllPlayersScore().pipe(takeUntil(this.destroyed$)).subscribe(r => console.log(r));
}
clearGameQueue() {
this.testingApiService.clearGameQueue().pipe(takeUntil(this.destroyed$)).subscribe((r => console.log(r)));
}
}

View file

@ -3,3 +3,8 @@
<div>tg: {{ gameQueue.type }}</div>
<button class="btn btn-dark" (click)="markAsCompleted(gameQueue._id)">complete</button>
</div>
<div *ngIf="versusData">
<h1>Who won</h1>
<button class="btn btn-dark m-2" (click)="versusWon(versusData.player1)">{{ versusData.player1name}}</button>
<button class="btn btn-dark m-2" (click)="versusWon(versusData.player2)">{{ versusData.player2name}}</button>
</div>

View file

@ -2,7 +2,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
import { EventService } from "../../../services/event.service";
import { Subject } from "rxjs";
import { map, takeUntil } from "rxjs/operators";
import { EventGameQueue, QueueTypes } from "../../../../types/server-event";
import {EventGameQueue, QueueTypes, VersusBeginEvent} from "../../../../types/server-event";
import { ApiService } from "../../../services/api.service";
@Component({
@ -14,7 +14,7 @@ export class QueueActionsComponent implements OnInit, OnDestroy {
destroyed$ = new Subject<void>()
constructor(private eventService: EventService, private apiService: ApiService) { }
gameQueue: EventGameQueue | null;
versusData: VersusBeginEvent| null = null;
ngOnInit(): void {
this.eventService.gameQueueEvent.pipe(
takeUntil(this.destroyed$),
@ -22,7 +22,17 @@ export class QueueActionsComponent implements OnInit, OnDestroy {
).subscribe(e => {
this.gameQueue = e;
});
this.setVersusHandler();
}
setVersusHandler() {
this.eventService.versusBegin.pipe(takeUntil(this.destroyed$)).subscribe((r) => {
this.versusData = r.data;
});
}
ngOnDestroy() {
this.destroyed$.complete();
}
@ -32,4 +42,10 @@ export class QueueActionsComponent implements OnInit, OnDestroy {
// this.gameQueue = null;
})
}
versusWon(playerId: number) {
this.apiService.completeVersus(playerId).subscribe(r => {
this.versusData = null;
})
}
}

View file

@ -1,4 +1,4 @@
<app-versus *ngIf="versusInProgress" [player1]="versusData.player1" [player2]="versusData.player2">
<app-versus *ngIf="versusData" [@enterAnimation] [player1]="versusData.player1" [player2]="versusData.player2">
</app-versus>
<app-toast>

View file

@ -10,18 +10,31 @@ import { ToastService } from "./toast.service";
import { VoiceService } from "./services/voice.service";
import { Subject } from "rxjs";
import { getAudioPath } from "./helper/tts.helper";
import {animate, keyframes, style, transition, trigger} from "@angular/animations";
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
styleUrls: ['./app.component.scss'],
animations: [
trigger(
'enterAnimation', [
transition(':enter', [
style({transform: 'translateX(100%)', opacity: 0}),
animate('500ms', style({transform: 'translateX(0)', opacity: 1}))
]),
transition(':leave', [
style({transform: 'translateX(0)', opacity: 1}),
animate('2000ms', style({transform: 'translateX(100%)', opacity: 0}))
])
]
)]
})
export class AppComponent implements OnInit, OnDestroy {
title = 'thanksgiving';
connection = io(WEBSOCK_URL, { transports: ['websocket']});
destroyed = new Subject<void>();
versusInProgress = false;
versusData: VersusBeginEvent;
versusData: VersusBeginEvent|null = null;
audioSrc: string;
constructor(
@ -70,12 +83,14 @@ export class AppComponent implements OnInit, OnDestroy {
}
private setupVersusHandler() {
console.log(this.routeSnapshot.snapshot.url);
this.eventService.versusBegin.pipe(takeUntil(this.destroyed)).subscribe(r => {
if(this.router.url.indexOf('admin') === -1) {
this.versusInProgress = true;
this.versusData = r.data;
}
})
}
this.eventService.versusBegin.pipe(takeUntil(this.destroyed)).subscribe(r => {
if (this.router.url.indexOf('admin') === -1) {
this.versusData = r.data;
}
})
this.eventService.versusEnd.pipe(takeUntil(this.destroyed)).subscribe((r) => {
console.log(r);
this.versusData = null;
})
}
}

View file

@ -2,7 +2,7 @@
.cards-history {
position: fixed;
z-index: 20000;
z-index: 1000;
width: 100%;
bottom: 0;
max-height: 70px;

View file

@ -3,7 +3,7 @@
<img [src]="getImageUrl()" class="participant-photo img-fluid">
</figure>
<div class="card-title">
{{ participant.name }}
{{ participant?.name }}
</div>
<div class="content" *ngIf="!small">
<div class="card-subtitle">

View file

@ -64,13 +64,18 @@ export class ParticipantItemComponent implements OnInit, OnDestroy, OnChanges {
}
getCards() {
this.apiService.getCards(this.participant.telegramId).subscribe((r) => {
this.cards = r;
})
if(this.participant) {
this.apiService.getCards(this.participant.telegramId).subscribe((r) => {
this.cards = r;
})
}
}
getImageUrl() {
return `${API_URL}/guests/photo/${this.participant.telegramId}?$t=${this.imgTimestamp}`;
if(this.participant) {
return `${API_URL}/guests/photo/${this.participant.telegramId}?$t=${this.imgTimestamp}`;
}
return null;
}
}

View file

@ -15,6 +15,7 @@ export class QuestionComponent implements OnInit, OnDestroy {
@Input() question: Question;
destroyed$ = new Subject<void>();
private questionSubscription: Subscription;
countdownInterval:ReturnType<typeof setInterval>|null= null;
countdown = 0;
readonly countDownTimer =20;
@ -31,20 +32,33 @@ export class QuestionComponent implements OnInit, OnDestroy {
this.countdown = -1;
});
this.countdown = this.countDownTimer;
setInterval(() => {
console.log(`question countdown`);
if(this.countdown === 0) {
this.continueGame();
}
this.countdown--;
}, 1000)
this.startCountdown();
this.questionSubscription = this.eventService.questionChangedEvent.subscribe(() =>{
this.getQuestion();
this.countdown = 20;
});
this.setUpVersusHandler();
}
startCountdown() {
this.countdown = this.countDownTimer;
this.countdownInterval = setInterval(() => {
if(this.countdown === 0) {
this.continueGame();
}
this.countdown--;
}, 1000)
}
setUpVersusHandler() {
this.eventService.versusBegin.pipe(takeUntil(this.destroyed$)).subscribe((r) => {
if(this.countdownInterval) {
clearInterval(this.countdownInterval);
}
});
this.eventService.versusBegin.pipe(takeUntil(this.destroyed$)).subscribe((r) => {
this.startCountdown();
})
}
continueGame() {

View file

@ -1,14 +1,24 @@
<div class="versus">
<div class="d-flex players">
<div class="player-one">
<div class="player-one" *ngIf=[playersLoaded] >
<app-participant-item [participant]="player1data" [small]="true" [shadow]="false" [transparent]="true">
</app-participant-item>
</div>
<div class="player-two">
<app-participant-item [participant]="player2data" [small]="true" [shadow]="false" [transparent]="true">
<div class="player-two" *ngIf=[playersLoaded]>
<app-participant-item [participant]="player2data" [small]="true" [shadow]="false" [transparent]="true">
</app-participant-item>
</div>
</div>
<div class="w-100 d-flex justify-content-center " *ngIf="versusData">
<div class="task row justify-content-center rounded-5 shadow p-2" >
<div class="col-12 text-center">
<h1>{{ versusData.text}}</h1>
</div>
<div class="col-12 text-center">
<div>{{ versusData.description }}</div>
</div>
</div>
</div>
</div>

View file

@ -25,6 +25,26 @@
}
}
@keyframes slideInUp {
0% {
bottom: -100px;
}
100% {
bottom: 25%;
}
}
@keyframes opacityIn {
0% {
opacity: 0;
}
100% {
opacity: 0.84;
}
}
.versus {
background-color: $thg_brown;
@ -66,7 +86,7 @@
color: white;
font-size: 2rem;
font-weight: bold;
animation: slideRight 1s ease forwards;
animation: slideRight 2s ease forwards;
}
/* Right player area */
@ -82,5 +102,15 @@
color: white;
font-size: 2rem;
font-weight: bold;
animation: slideLeft 1s ease forwards;
animation: slideLeft 2s ease forwards;
}
.task {
position: absolute;
bottom: 0;
height: 10%;
width: 50%;
background-color: white;
opacity: 0.84;
animation: slideInUp 3s ease forwards, opacityIn 1500ms linear;
}

View file

@ -1,8 +1,9 @@
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
import {ApiService} from "../../services/api.service";
import {Subject} from "rxjs";
import {takeUntil} from "rxjs/operators";
import {combineLatest, Subject} from "rxjs";
import {combineAll, takeUntil} from "rxjs/operators";
import {Participant} from "../../../types/participant";
import {VersusItem} from "../../../types/versus-item";
@Component({
selector: 'app-versus',
@ -15,18 +16,35 @@ export class VersusComponent implements OnInit, OnDestroy{
player1data: Participant;
player2data: Participant;
destroyed$ = new Subject<void>();
playersLoaded = false;
versusData: VersusItem | null = null;
constructor(private apiService: ApiService) {
}
ngOnInit() {
this.loadPlayersData();
this.loadTask()
}
ngOnDestroy() {
this.destroyed$.complete();
}
loadPlayersData() {
this.apiService.getParticipant(this.player1).pipe(takeUntil(this.destroyed$)).subscribe((r) => this.player1data = r);
this.apiService.getParticipant(this.player2).pipe(takeUntil(this.destroyed$)).subscribe((r) => this.player2data = r);
const player1Data$ = this.apiService.getParticipant(this.player1);
const player2Data$ = this.apiService.getParticipant(this.player2);
combineLatest([player1Data$,player2Data$]).pipe(takeUntil(this.destroyed$)).subscribe(([d1,d2]) => {
this.player1data = d1;
this.player2data = d2;
this.playersLoaded = true;
})
}
private loadTask() {
setTimeout(() => {
this.apiService.getVersus().pipe(takeUntil(this.destroyed$)).subscribe((r) => {
this.versusData = r;
})
}, 1500);
}
}

View file

@ -10,12 +10,30 @@ import { GameState } from "./gameState";
import { PenaltyDto } from "../../types/penalty.dto";
import { PrizeDto } from "../../types/prize.dto";
import {QuestionresultsDto} from "../../types/questionresults.dto";
import {map} from "rxjs/operators";
import {VersusItem} from "../../types/versus-item";
export interface FeatureFlagStateDto {
name: string;
state: boolean;
}
export interface StateInformationDto<T extends { action: string}> {
key: string;
value: { key: string; value: T };
}
export interface StateInformationVersusDto {
action: any;
player1: number;
player2: number;
}
export interface ConfigRecordDto {
key: string;
value: string;
}
@Injectable({
providedIn: 'root'
})
@ -107,4 +125,18 @@ export class ApiService {
setFeatureFlagState(feature: string, state: boolean) {
return this.httpClient.post<FeatureFlagStateDto>(`${API_URL}/featureflag`, { name: feature, state: state });
}
getStateDetails() {
return this.httpClient.get<ConfigRecordDto>(`${API_URL}/game/state-details`);
}
getVersus() {
return this.httpClient.get<VersusItem>(`${API_URL}/versus`);
}
completeVersus(winner: number) {
return this.httpClient.post(`${API_URL}/versus/complete`, {
winner: winner
});
}
}

View file

@ -33,6 +33,7 @@ export class EventService {
public userPropertyChanged = new EventEmitter<ServerEvent<UserPropertyChanged>>();
public featureFlagChanged = new EventEmitter<ServerEvent<void>>()
public versusBegin = new EventEmitter<ServerEvent<VersusBeginEvent>>();
public versusEnd = new EventEmitter<ServerEvent<{ winner: number }>>
constructor() { }
public emit(event: ServerEvent<any>) {
@ -89,6 +90,9 @@ export class EventService {
case "begin_versus":
this.versusBegin.emit(event as ServerEvent<VersusBeginEvent>);
break;
case "end_versus":
this.versusEnd.emit(event as ServerEvent<{winner: number}>);
break;
}
}
}

View file

@ -10,6 +10,18 @@ export class TestingApiService {
constructor(private httpClient: HttpClient) { }
public simulateVersus() {
return this.httpClient.post(`${API_URL}/game/simulate-versus`, {});
return this.httpClient.post(`${API_URL}/versus/simulate-versus`, {});
}
resetAllVersusTasksAsIncompleted() {
return this.httpClient.post(`${API_URL}/versus/reset-all`, {});
}
resetAllPlayersScore() {
return this.httpClient.post(`${API_URL}/guests/reset-score`, {});
}
clearGameQueue() {
return this.httpClient.post(`${API_URL}/game/clear-queue`, {});
}
}

View file

@ -54,6 +54,8 @@ export interface EventScoreChanged {
export interface VersusBeginEvent {
player1: number;
player2: number;
player1name: string;
player2name: string;
}
export interface EventGameQueue {
@ -94,5 +96,6 @@ export interface ServerEvent<T> {
| 'user_property_changed'
| 'feature_flag_changed'
| 'begin_versus'
| 'end_versus'
data: T
}

6
src/types/versus-item.ts Normal file
View file

@ -0,0 +1,6 @@
export interface VersusItem {
text: string;
name: string;
completed: boolean;
description: string;
}