Browse Source

Inital pass at standings, countdown timer

new_auth
Dan 2 years ago
parent
commit
2d53a13d82
  1. 4
      packages/bridge-server/src/season-standings/season-standing.model.ts
  2. 47
      packages/bridge-server/src/season-standings/season-standings.service.ts
  3. 3
      packages/bridge-server/src/seasons/seasons.service.ts
  4. 2
      packages/bridge-server/src/upload/upload.controller.ts
  5. 30
      packages/bridge-server/src/upload/upload.service.ts
  6. 33
      packages/bridge-ui/src/app/components/race-details/race-details.component.html
  7. 53
      packages/bridge-ui/src/app/components/race-details/race-details.component.scss
  8. 91
      packages/bridge-ui/src/app/components/race-details/race-details.component.ts
  9. 2
      packages/bridge-ui/src/app/components/season-standings/season-standings.component.html
  10. 22
      packages/bridge-ui/src/app/components/season-standings/season-standings.component.ts
  11. 4
      packages/bridge-ui/src/app/components/upload-replay-dialog/upload-replay-dialog.component.ts
  12. 6
      packages/bridge-ui/src/app/models/season-standing.model.ts
  13. 3
      packages/bridge-ui/src/app/models/season.model.ts
  14. 2
      packages/bridge-ui/src/app/pages/season-details/season-details.component.html
  15. 2
      packages/bridge-ui/src/app/services/server-endpoint.service.ts

4
packages/bridge-server/src/season-standings/season-standing.model.ts

@ -1,10 +1,11 @@
import { Column, Model, Table, HasMany, ForeignKey, BelongsTo } from "sequelize-typescript";
import { Column, Model, Table, HasMany, ForeignKey, BelongsTo, PrimaryKey } from "sequelize-typescript";
import { Racer } from "src/racers/racer.model";
import { Season } from "src/seasons/season.model";
@Table
export class SeasonStanding extends Model {
@ForeignKey(() => Season)
@PrimaryKey
@Column
seasonId: number;
@ -12,6 +13,7 @@ export class SeasonStanding extends Model {
season: Season
@ForeignKey(() => Racer)
@PrimaryKey
@Column
racerId: number;

47
packages/bridge-server/src/season-standings/season-standings.service.ts

@ -4,6 +4,8 @@ import { SeasonStanding } from './season-standing.model';
import { Sequelize } from 'sequelize-typescript';
import { RaceResultsService } from 'src/race-results/race-results.service';
import { RacesService } from 'src/races/races.service';
import { Racer } from 'src/racers/racer.model';
import { RaceResult } from 'src/race-results/race-result.model';
@Injectable()
export class SeasonStandingsService {
@ -16,6 +18,13 @@ export class SeasonStandingsService {
)
{}
async findManyForSeason(seasonId) {
return this.seasonStandingModel.findAll({
where: { seasonId : seasonId },
include:[ Racer ]
})
}
async findOrCreate(racerId: number, seasonId: number): Promise<SeasonStanding> {
try {
return this.sequelize.transaction( async t => {
@ -35,14 +44,46 @@ export class SeasonStandingsService {
}
async updateStandings(seasonId: number) {
let racesInSeason = await this.racesService.findManyBySeason(seasonId);
console.log(racesInSeason);
let seasonRacers = [];
let seasonRacers: Map<number, Racer> = new Map<number, Racer>();
let seasonRaceResults: Map<number, RaceResult[]> = new Map<number, RaceResult[]>();
for(let race of racesInSeason) {
for( let result of race.results ){
// if ( )
let fastest = await this.raceResultsService.getFastestTimesForRace(race.id);
seasonRaceResults.set(race.id, fastest);
for(let result of fastest ) {
if( seasonRacers.has(result.racerId) == false ) {
seasonRacers.set(result.racerId, result.racer);
}
}
}
let maxRacePoints = seasonRacers.size;
let seasonPoints = new Map<number, any>();
for( let results of seasonRaceResults ) {
let availablePoints = maxRacePoints;
results[1].sort((a,b) => {
return a.time - b.time;
});
for( let result of results[1] ) {
if (seasonPoints.has(result.racerId) == false) {
seasonPoints.set(result.racerId, { points: 0 });
}
let currentPoints = seasonPoints.get(result.racerId);
if( currentPoints == undefined ) {
continue;
}
currentPoints.points+=availablePoints;
availablePoints -=1;
}
}
for( let entry of seasonPoints) {
console.log(entry);
this.seasonStandingModel.upsert({seasonId: seasonId, racerId: entry[0], points: entry[1].points});
}
}
}

3
packages/bridge-server/src/seasons/seasons.service.ts

@ -4,6 +4,7 @@ import { Season } from './season.model';
import { SeasonStanding } from 'src/season-standings/season-standing.model';
import { Race } from 'src/races/race.model';
import { Sequelize } from 'sequelize-typescript';
import { Racer } from 'src/racers/racer.model';
@Injectable()
export class SeasonsService {
@ -18,7 +19,7 @@ export class SeasonsService {
}
async findOne(id: number) {
return this.seasonModel.findOne({ where: {id: id} , include:{ all: true } })
return this.seasonModel.findOne({ where: {id: id} , include:[{model: SeasonStanding, include:[Racer]}, Race] })
}
async create(title: string, subTitle: string, startingDate: Date) {

2
packages/bridge-server/src/upload/upload.controller.ts

@ -14,6 +14,6 @@ export class UploadController {
@Post('replay')
@UseInterceptors(FileInterceptor('file'))
uploadFile(@UploadedFile() file: Express.Multer.File, @Body() body: any) {
this.uploadService.replayUploaded(file, body);
return this.uploadService.replayUploaded(file, body);
}
}

30
packages/bridge-server/src/upload/upload.service.ts

@ -48,36 +48,6 @@ export class UploadService {
console.log(result);
this.seasonStandingsService.updateStandings(body.seasonId);
/*
fs.readFile(file.path, async function(err, buffer)
{
console.log(err)
console.log(buffer)
let buff = new AdvancableBuffer(buffer);
let header = new gbxHeader();
header.parse(buff);
if (header.is_vaild == false)
{
return;
}
console.log(header);
let replay = new gbxReplay();
replay.parse(buff);
let currentRacer = await this.racersService.findOrCreate(replay.gamerHandle);
console.log(currentRacer);
let race = await this.racesService.findOne({season: body.seasonId, mapUID: replay.mapUID})
console.log(race);
let result = await this.raceResultsService.create({race: race, racer: currentRacer, timeInMilliseconds: replay.bestTime, replayPath: file.destination});
console.log(result);
this.seasonStandingsService.updateStandings(body.seasonId);
//res.status(200);
});
*/
}
}

33
packages/bridge-ui/src/app/components/race-details/race-details.component.html

@ -5,10 +5,10 @@
<h1>{{race.mapName}}</h1>
</div>
<div class="map-times">
<p>Author Time: {{race.authorTime}}</p>
<p>Gold Time: {{race.goldTime}}</p>
<p>Silver Time: {{race.silverTime}}</p>
<p>Bronze Time: {{race.bronzeTime}}</p>
<p>Author Time: {{formatMilliseconds(race.authorTime)}}</p>
<p>Gold Time: {{formatMilliseconds(race.goldTime)}}</p>
<p>Silver Time: {{formatMilliseconds(race.silverTime)}}</p>
<p>Bronze Time: {{formatMilliseconds(race.bronzeTime)}}</p>
</div>
</div>
<img
@ -16,7 +16,30 @@
class="img-thumbnail shadow-2-strong"
/>
</div>
<br>
<div class="count-down-timer">
@if(openToUploads) {
<h2>Entries Close on: {{currentTime}} </h2>
<div class="wrapper">
<div class="description">
<p>Days</p>
<p>Hours</p>
<p>Minutes</p>
<p>Seconds</p>
</div>
<div class="times">
<p #days></p>
<p #hours></p>
<p #minutes></p>
<p #seconds></p>
</div>
</div>
}
@else {
<h2>Closed for new entries</h2>
}
</div>
<br/>
<div>
<table mat-table [dataSource]="sortedResults" class="mat-elevation-z8">

53
packages/bridge-ui/src/app/components/race-details/race-details.component.scss

@ -1,6 +1,7 @@
.img-thumbnail {
width: 200px;
//float: right;
box-shadow: 2px 6px 9px 2px rgb(0 0 0 / 20%);
}
.race-details {
@ -22,4 +23,54 @@
.map-times {
margin-left: 15px;
}
}
.count-down-timer {
box-shadow: 2px 6px 9px 2px rgb(0 0 0 / 20%);
text-align: center;
background-color: var(--mat-table-background-color);
max-width: 400px;
margin: 20px auto;
//color: #d9a74a;
border-radius: 6px;
padding: 10px;
font-family: sans-serif;
> p {
margin: 5px 0 15px 0;
}
.wrapper {
.description,
.times {
display: grid;
grid-template-columns: repeat(4, calc(25% - 8px));
grid-column-gap: 10px;
}
.description {
> p {
margin: 0;
font: normal 14px sans-serif;
}
}
.times {
p {
letter-spacing: -5px;
position: relative;
margin: 0;
font: normal 40px courier, sans-serif;
::ng-deep img {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 30px;
display: block;
height: 30px;
}
}
}
}
}

91
packages/bridge-ui/src/app/components/race-details/race-details.component.ts

@ -1,4 +1,4 @@
import { Component, Input, OnInit } from '@angular/core';
import { AfterViewInit, Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
@ -7,7 +7,6 @@ import { MatIconModule } from '@angular/material/icon';
import { MatTableModule } from '@angular/material/table';
import { RacesService } from '../../services/races.service';
import { RacersService } from '../../services/racers.service';
import { RaceResultService } from '../../services/race-result.service';
import { Race } from '../../models/race.model';
import { Racer } from '../../models/racer.model';
@ -27,27 +26,66 @@ import { RaceEntry } from '../../models/raceEntry.model';
styleUrl: './race-details.component.scss'
})
export class RaceDetailsComponent {
export class RaceDetailsComponent implements AfterViewInit {
@Input() race?: Race;
racers: Map<string, Racer> = new Map<string, Racer>();
//raceResults: Map<string, RaceEntry[]> = new Map<string, RaceEntry[]>;
raceResults: any;
blob: Blob = new Blob();
displayedColumns: string[] = ['position', 'name', 'runTime', 'ghost'];
sortedResults: RaceEntry[] = [];
openToUploads: boolean = true;
date: any;
now: any;
targetDate: Date = new Date(2024, 5, 11);
targetTime: number = this.targetDate.getTime();
difference: number = 0;
months: Array<string> = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
];
currentTime: any = `${
this.months[this.targetDate.getMonth()]
} ${this.targetDate.getDate()}, ${this.targetDate.getFullYear()}`;
@ViewChild('days') days?: ElementRef;
@ViewChild('hours') hours?: ElementRef;
@ViewChild('minutes') minutes?: ElementRef;
@ViewChild('seconds') seconds?: ElementRef;
constructor(
private racersService: RacersService,
private raceResultService: RaceResultService,
private racesService: RacesService,
) {}
ngOnInit() {
if( this.race == undefined) {
console.log("No race in race-details")
return;
}
this.targetDate = new Date(this.race.endDate);
this.targetTime = this.targetDate.getTime();
if(this.targetTime < Date.now()){
this.openToUploads = false;
}
this.currentTime = `${
this.months[this.targetDate.getMonth()]
} ${this.targetDate.getDate()}, ${this.targetDate.getFullYear()}`;
this.racesService.getRaceResults(this.race.id).subscribe(data => {
console.log(data)
this.raceResults = data;
@ -65,22 +103,33 @@ export class RaceDetailsComponent {
})
}
getBestTimeForRacer(racer_id: string): RaceEntry | undefined {
let results = this.raceResults.get(racer_id)
if (results == undefined ){
return undefined
ngAfterViewInit(): void {
if(this.openToUploads == false) {
return;
}
let bestTime: number = 0xFFFFFFFF;
let bestResult = undefined
for(let result of results){
if(result.time < bestTime) {
bestTime = result.time
bestResult = result;
setInterval(() => {
this.tickTock();
this.difference = this.targetTime - this.now;
this.difference = this.difference / (1000 * 60 * 60 * 24);
if(this.days && this.hours && this.minutes && this.seconds){
!isNaN(this.days.nativeElement.innerText)
? (this.days.nativeElement.innerText = Math.floor(this.difference))
: (this.days.nativeElement.innerHTML = `<img src="https://i.gifer.com/VAyR.gif" />`);
}
}
}, 1000);
}
return bestResult
tickTock() {
this.date = new Date();
this.now = this.date.getTime();
if(this.days != undefined && this.hours != undefined && this.minutes != undefined && this.seconds != undefined) {
this.days.nativeElement.innerText = Math.floor(this.difference);
this.hours.nativeElement.innerText = 23 - this.date.getHours();
this.minutes.nativeElement.innerText = 60 - this.date.getMinutes();
this.seconds.nativeElement.innerText = 60 - this.date.getSeconds();
}
}
formatMilliseconds(milliseconds: number)
@ -104,8 +153,6 @@ export class RaceDetailsComponent {
return ""
}
blob: Blob = new Blob();
onClickDownloadGhost(raceResult: any) {
console.log(raceResult)
this.raceResultService.getGhost(raceResult.id).subscribe(data => {

2
packages/bridge-ui/src/app/components/season-standings/season-standings.component.html

@ -8,7 +8,7 @@
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>Name </th>
<td mat-cell *matCellDef="let element"> {{getRacerName(element.id)}} </td>
<td mat-cell *matCellDef="let element"> {{getRacerName(element.racer)}} </td>
</ng-container>
<!-- Weight Column -->

22
packages/bridge-ui/src/app/components/season-standings/season-standings.component.ts

@ -10,12 +10,7 @@ import { Racer } from '../../models/racer.model';
import { RaceEntry } from '../../models/raceEntry.model';
import { Season } from '../../models/season.model';
import { RacesService } from '../../services/races.service';
class SeasonStanding {
id: string = "";
points: number = 0;
constructor(_id: string) { this.id = _id;}
}
import { SeasonStanding } from '../../models/season-standing.model';
@Component({
selector: 'app-season-standings',
@ -56,6 +51,12 @@ export class SeasonStandingsComponent {
return;
}
this.sortedStandings = [...this.season.standings]
console.log("Season Standings - Begin")
console.log(this.season);
console.log(this.sortedStandings);
console.log("Season Standings - end")
return;
/*
for (let race_id of this.season.races) {
@ -92,9 +93,12 @@ export class SeasonStandingsComponent {
}*/
}
getRacerName(racerId: string): string {
let racer = this.seasonRacers.get(racerId);
getRacerName(racer: any): string {
if( racer != undefined) {
if( racer.name == undefined || racer.name == "" ) {
return racer.gameHandle;
}
return racer.name + " (" + racer.gameHandle + ")"
}
@ -114,6 +118,7 @@ export class SeasonStandingsComponent {
return bestResult
}
/*
calculateSeasonPoints() {
if(this.races == undefined) {
@ -155,4 +160,5 @@ export class SeasonStandingsComponent {
this.sortedStandings = [...this.sortedStandings]
}
*/
}

4
packages/bridge-ui/src/app/components/upload-replay-dialog/upload-replay-dialog.component.ts

@ -65,6 +65,8 @@ export class UploadReplayDialogComponent {
let local = this.seasonId;
formData.append("seasonId", local);
const upload$ = this.replaysService.uploadReplay(formData);
upload$.subscribe();
upload$.subscribe(data => {
window.location.reload();
});
}
}

6
packages/bridge-ui/src/app/models/season-standing.model.ts

@ -0,0 +1,6 @@
import { Racer } from "./racer.model";
export class SeasonStanding {
racer?: Racer;
points: number = 0;
}

3
packages/bridge-ui/src/app/models/season.model.ts

@ -1,4 +1,5 @@
import { Race } from "./race.model";
import { SeasonStanding } from "./season-standing.model";
export class Season {
id: string = "0";
@ -7,5 +8,5 @@ export class Season {
startingDate: Date = new Date(Date.now());
races: Race[] = [];
racers: string[] = [];
standings: string[] = [];
standings: SeasonStanding[] = [];
}

2
packages/bridge-ui/src/app/pages/season-details/season-details.component.html

@ -10,7 +10,7 @@
<br/>
}
<button mat-raised-button class="full-width-button" color="primary" (click)="openUploadReplayDialog(season.id)">Upload Replay</button>
<button mat-raised-button class="full-width-button" color="accent" (click)="openUploadReplayDialog(season.id)">Upload Replay</button>
<mat-divider></mat-divider>
<br/>

2
packages/bridge-ui/src/app/services/server-endpoint.service.ts

@ -10,7 +10,7 @@ export class ServerEndpointService {
constructor()
{
this.currentHost = "localhost";
this.currentHost = "172.16.40.146";
this.currentPort = "3000";
}

Loading…
Cancel
Save