Compare commits
33 Commits
d0c485c0e9
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
8d6129590a
|
|||
|
13aa2c29b0
|
|||
|
5f256e1983
|
|||
|
4dbc6a2a4d
|
|||
|
e26cd21cc6
|
|||
|
139fb20814
|
|||
|
1027011447
|
|||
|
b09b683727
|
|||
|
aec84dca5d
|
|||
|
7082abcdf7
|
|||
|
7c3404acb0
|
|||
|
e8165727de
|
|||
|
6d2c750a58
|
|||
|
7f436ae0b8
|
|||
|
753e490c29
|
|||
|
051657ef65
|
|||
|
e41a07411e
|
|||
|
cd5154a07a
|
|||
|
ae45cc78a1
|
|||
| d0df6e3e5c | |||
| 5c675782aa | |||
|
68e35fc5bc
|
|||
|
a9aca1351c
|
|||
|
bed8992ddd
|
|||
|
f69f7f1731
|
|||
|
c6302ad8c0
|
|||
|
14696fc116
|
|||
|
5449cd7714
|
|||
|
0e6d1b4390
|
|||
| 080fa80f03 | |||
| 9c3547f712 | |||
|
b300fe1b7b
|
|||
| 0889c101aa |
Generated
+32
-11958
File diff suppressed because it is too large
Load Diff
@@ -3,11 +3,13 @@ import {RouterModule, Routes} from '@angular/router';
|
|||||||
import {GamenightComponent} from './pages/gamenight/gamenight.component';
|
import {GamenightComponent} from './pages/gamenight/gamenight.component';
|
||||||
import {HomeComponent} from './pages/home/home.component';
|
import {HomeComponent} from './pages/home/home.component';
|
||||||
import {StatsComponent} from './pages/stats/stats.component';
|
import {StatsComponent} from './pages/stats/stats.component';
|
||||||
|
import {MatchHistoryComponent} from "./pages/match-history/match-history.component";
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{path: '', component: HomeComponent, pathMatch: 'full'},
|
{path: '', component: HomeComponent, pathMatch: 'full'},
|
||||||
{path: 'gamenight', component: GamenightComponent},
|
{path: 'gamenight', component: GamenightComponent},
|
||||||
{path: 'stats', component: StatsComponent}
|
{path: 'stats', component: StatsComponent},
|
||||||
|
{path: 'match-history', component: MatchHistoryComponent}
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ import {StatsComponent} from './pages/stats/stats.component';
|
|||||||
import {GamenightComponent} from './pages/gamenight/gamenight.component';
|
import {GamenightComponent} from './pages/gamenight/gamenight.component';
|
||||||
import {HeaderComponent} from './components/header/header.component';
|
import {HeaderComponent} from './components/header/header.component';
|
||||||
import {ProfileComponent} from './components/profile/profile.component';
|
import {ProfileComponent} from './components/profile/profile.component';
|
||||||
|
import {AddGameComponent} from './components/add-game/add-game.component';
|
||||||
|
import {MatchHistoryComponent} from './pages/match-history/match-history.component';
|
||||||
|
import {FormsModule} from '@angular/forms';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@@ -16,11 +19,14 @@ import {ProfileComponent} from './components/profile/profile.component';
|
|||||||
StatsComponent,
|
StatsComponent,
|
||||||
GamenightComponent,
|
GamenightComponent,
|
||||||
HeaderComponent,
|
HeaderComponent,
|
||||||
ProfileComponent
|
ProfileComponent,
|
||||||
|
AddGameComponent,
|
||||||
|
MatchHistoryComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
AppRoutingModule
|
AppRoutingModule,
|
||||||
|
FormsModule
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<app-header></app-header>
|
||||||
|
<div id="add-game">
|
||||||
|
<div id="game-infos">
|
||||||
|
<!-- The following divs get displayed one after another, kinda wizard-like -->
|
||||||
|
<div id="which-players" class="visible-{{this.currentPage === 0}}">
|
||||||
|
<p>Select the active players for the game:</p>
|
||||||
|
<div class="togglebtn active-{{isPlayerActive(player)}}" *ngFor="let player of potentialPlayers" (click)="togglePlayer(player)">{{player.firstName}}</div>
|
||||||
|
<p id="player-amount-warn" *ngIf="not4Players()">Illegal amount of players!</p>
|
||||||
|
<button (click)="switchToNextPage()" [disabled]="not4Players()">Next</button>
|
||||||
|
</div>
|
||||||
|
<div id="announcements" class="visible-{{this.currentPage === 1}}">
|
||||||
|
<p>Players: {{getPlayerNamesAsString()}}</p>
|
||||||
|
<p>Select the announcements for this game:</p>
|
||||||
|
<div class="togglebtn active-{{isAnnouncementActive(announcement)}}" *ngFor="let announcement of getAllPossibleAnnouncements()" (click)="toggleAnnouncement(announcement)">{{announcement.toString()}}</div>
|
||||||
|
<p id="announcement-warn" *ngIf="!checkAnnouncementsValid()">Illegal set of announcements!</p>
|
||||||
|
<button (click)="switchToNextPage()" [disabled]="!checkAnnouncementsValid()">Next</button>
|
||||||
|
</div>
|
||||||
|
<div id="player-teams" class="visible-{{this.currentPage === 2}}">
|
||||||
|
<p>Players: {{getPlayerNamesAsString()}}</p>
|
||||||
|
<p>Highest Announcements: {{getHighestAnnouncements()}}</p>
|
||||||
|
<p>Please select the elder(s):</p>
|
||||||
|
<div class="togglebtn elder-player-{{isPlayerElder(player)}}" *ngFor="let player of actualPlayers" (click)="toggleElderPlayer(player)">{{player.firstName}}</div>
|
||||||
|
<p id="team-warn" *ngIf="!checkValidTeamAssignment()">Illegal game teams!</p>
|
||||||
|
<button (click)="switchToNextPage()" [disabled]="!checkValidTeamAssignment()">Next</button>
|
||||||
|
</div>
|
||||||
|
<div id="player-card-scores" class="visible-{{this.currentPage === 3}}">
|
||||||
|
<p>Please enter the points that every player has collected:</p>
|
||||||
|
<table class="point-entry">
|
||||||
|
<tr *ngIf="this.actualPlayers[0]">
|
||||||
|
<td><p>{{this.actualPlayers[0].firstName}}</p></td>
|
||||||
|
<td><input type="number" [(ngModel)]="this.actualPlayers[0].finalCardScore" (change)="this.calculateCurrentScores()"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="this.actualPlayers[1]">
|
||||||
|
<td><p>{{this.actualPlayers[1].firstName}}</p></td>
|
||||||
|
<td><input type="number" [(ngModel)]="this.actualPlayers[1].finalCardScore" (change)="this.calculateCurrentScores()"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="this.actualPlayers[2]">
|
||||||
|
<td><p>{{this.actualPlayers[2].firstName}}</p></td>
|
||||||
|
<td><input type="number" [(ngModel)]="this.actualPlayers[2].finalCardScore" (change)="this.calculateCurrentScores()"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="this.actualPlayers[3]">
|
||||||
|
<td><p>{{this.actualPlayers[3].firstName}}</p></td>
|
||||||
|
<td><input type="number" [(ngModel)]="this.actualPlayers[3].finalCardScore" (change)="this.calculateCurrentScores()"/></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<p id="score-warn" *ngIf="calculatePointSum() !== 240">Total score doesn't add up ({{getScoreDifferenceText()}})</p>
|
||||||
|
<button (click)="switchToNextPage()" [disabled]="!calculatePointSum()">Next</button>
|
||||||
|
</div>
|
||||||
|
<div id="which-solo" class="visible-{{this.currentPage === 4}}">
|
||||||
|
<p>Select the Solo that has been played:</p>
|
||||||
|
<div class="togglebtn active-{{isSoloActive(solo)}}" *ngFor="let solo of getAllPossibleSolos()" (click)="toggleSolo(solo)">{{solo.toString()}}</div>
|
||||||
|
<p id="solo-warn" *ngIf="noSoloSelectedYet()">Please select a solo to continue</p>
|
||||||
|
<button (click)="switchToNextPage()" [disabled]="noSoloSelectedYet()">Next</button>
|
||||||
|
</div>
|
||||||
|
<div id="extra-points" class="visible-{{this.currentPage === 5}}">
|
||||||
|
<p>Extra Points</p>
|
||||||
|
<div *ngFor="let player of actualPlayers">
|
||||||
|
<p>{{player.firstName}}</p>
|
||||||
|
<input type="number" [(ngModel)]="player.foxesCaught" (change)="this.calculateCurrentScores()"/>
|
||||||
|
</div>
|
||||||
|
<p id="foxes-warn" *ngIf="!checkTotalFoxPoints()">Please check the number of caught foxes!</p>
|
||||||
|
<button (click)="switchToNextPage()" [disabled]="!checkAllExtraPoints()">Next</button>
|
||||||
|
</div>
|
||||||
|
<div id="summary" class="visible-{{this.currentPage === 6}}">
|
||||||
|
<p>Game Summary</p>
|
||||||
|
<button (click)="saveGame()">Save Game</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="scores">
|
||||||
|
<p class="team-{{player.team}}" *ngFor="let player of actualPlayers">
|
||||||
|
{{player.firstName}}: {{player.gamePoints}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
@import '../../../styles';
|
||||||
|
|
||||||
|
#add-game {
|
||||||
|
padding-top: $header_height;
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas: 'entry entry scores';
|
||||||
|
}
|
||||||
|
|
||||||
|
#game-infos {
|
||||||
|
grid-area: entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
#scores {
|
||||||
|
grid-area: scores;
|
||||||
|
}
|
||||||
|
|
||||||
|
#which-players div {
|
||||||
|
padding: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#which-players div:hover {
|
||||||
|
color: $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-true, .elder-player-true {
|
||||||
|
color: $active;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-false, .elder-player-false {
|
||||||
|
color: $inactive;
|
||||||
|
}
|
||||||
|
|
||||||
|
#player-amount-warn, #announcement-warn, #team-warn, #score-warn, #solo-warn {
|
||||||
|
color: $warn;
|
||||||
|
}
|
||||||
|
|
||||||
|
.visible-false {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.visible-true {
|
||||||
|
display: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.togglebtn {
|
||||||
|
text-align: center;
|
||||||
|
max-width: 10em;
|
||||||
|
background-color: $button;
|
||||||
|
margin: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-Re {
|
||||||
|
background-color: $secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-Contra {
|
||||||
|
background-color: $primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.point-entry td {
|
||||||
|
padding: .5em;
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AddGameComponent } from './add-game.component';
|
||||||
|
import {Team} from '../../models/doppelkopf/enums/team';
|
||||||
|
|
||||||
|
describe('AddGameComponent', () => {
|
||||||
|
let component: AddGameComponent;
|
||||||
|
let fixture: ComponentFixture<AddGameComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ AddGameComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(AddGameComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have the correct score for a normal game', () => {
|
||||||
|
// Set up testing data
|
||||||
|
component.actualPlayers = [
|
||||||
|
{
|
||||||
|
firebonkId: 1,
|
||||||
|
uuid: 'abc-def-ghi-j',
|
||||||
|
firstName: 'Patrick',
|
||||||
|
team: Team.RE,
|
||||||
|
gamePoints: 0,
|
||||||
|
finalCardScore: 123,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
firebonkId: 1,
|
||||||
|
uuid: 'abc-def-ghi-k',
|
||||||
|
firstName: 'Julian',
|
||||||
|
team: Team.RE,
|
||||||
|
gamePoints: 17,
|
||||||
|
finalCardScore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
firebonkId: 1,
|
||||||
|
uuid: 'abc-def-ghi-l',
|
||||||
|
firstName: 'Yanick',
|
||||||
|
team: Team.CONTRA,
|
||||||
|
gamePoints: 50,
|
||||||
|
finalCardScore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
firebonkId: 1,
|
||||||
|
uuid: 'abc-def-ghi-m',
|
||||||
|
firstName: 'Janina',
|
||||||
|
team: Team.CONTRA,
|
||||||
|
gamePoints: 50,
|
||||||
|
finalCardScore: 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
component.calculateCurrentScores();
|
||||||
|
|
||||||
|
expect(component.actualPlayers[0].gamePoints).toEqual(1);
|
||||||
|
expect(component.actualPlayers[1].gamePoints).toEqual(1);
|
||||||
|
expect(component.actualPlayers[2].gamePoints).toEqual(-1);
|
||||||
|
expect(component.actualPlayers[3].gamePoints).toEqual(-1);
|
||||||
|
})
|
||||||
|
});
|
||||||
@@ -0,0 +1,723 @@
|
|||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {Player} from '../../models/doppelkopf/player';
|
||||||
|
import * as announcements from '../../models/doppelkopf/enums/announcement';
|
||||||
|
import * as solos from '../../models/doppelkopf/enums/solo';
|
||||||
|
import {Team} from '../../models/doppelkopf/enums/team';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-add-game',
|
||||||
|
templateUrl: './add-game.component.html',
|
||||||
|
styleUrls: ['./add-game.component.scss']
|
||||||
|
})
|
||||||
|
export class AddGameComponent implements OnInit {
|
||||||
|
potentialPlayers: Player[] = [];
|
||||||
|
actualPlayers: Player[] = [];
|
||||||
|
|
||||||
|
selectedAnnouncements: announcements.Announcement[] = [];
|
||||||
|
soloPlayed?: solos.Solo;
|
||||||
|
|
||||||
|
currentPage: number = 0;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.generateDemoData();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates demo data to test the UI
|
||||||
|
*/
|
||||||
|
generateDemoData() {
|
||||||
|
this.potentialPlayers.push({
|
||||||
|
firebonkId: 1,
|
||||||
|
uuid: 'abc-def-ghi-j',
|
||||||
|
firstName: 'Patrick',
|
||||||
|
team: Team.CONTRA,
|
||||||
|
gamePoints: 0,
|
||||||
|
finalCardScore: 0,
|
||||||
|
// foxesCaught: 1,
|
||||||
|
// wonLastTrickWithCharlie: true
|
||||||
|
});
|
||||||
|
this.potentialPlayers.push({
|
||||||
|
firebonkId: 2,
|
||||||
|
uuid: 'abc-def-ghi-k',
|
||||||
|
firstName: 'Julian',
|
||||||
|
team: Team.CONTRA,
|
||||||
|
gamePoints: 0,
|
||||||
|
finalCardScore: 0
|
||||||
|
});
|
||||||
|
this.potentialPlayers.push({
|
||||||
|
firebonkId: 3,
|
||||||
|
uuid: 'abc-def-ghi-l',
|
||||||
|
firstName: 'Yannick',
|
||||||
|
team: Team.CONTRA,
|
||||||
|
gamePoints: 0,
|
||||||
|
finalCardScore: 0
|
||||||
|
});
|
||||||
|
this.potentialPlayers.push({
|
||||||
|
firebonkId: 4,
|
||||||
|
uuid: 'abc-def-ghi-m',
|
||||||
|
firstName: 'Janina',
|
||||||
|
team: Team.CONTRA,
|
||||||
|
gamePoints: 0,
|
||||||
|
finalCardScore: 0
|
||||||
|
});
|
||||||
|
this.potentialPlayers.push({
|
||||||
|
firebonkId: 5,
|
||||||
|
uuid: 'abc-def-ghi-n',
|
||||||
|
firstName: 'Moritz',
|
||||||
|
team: Team.CONTRA,
|
||||||
|
gamePoints: 0,
|
||||||
|
finalCardScore: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
this.actualPlayers.push(...this.potentialPlayers.slice(0, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switches the entry mask UI to the next page
|
||||||
|
*/
|
||||||
|
switchToNextPage(): void {
|
||||||
|
if (this.currentPage >= 4) {
|
||||||
|
this.calculateCurrentScores();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.currentPage === 3 && !this.checkIfSolo()) {
|
||||||
|
// If we don't play a solo, we can skip the solo page
|
||||||
|
this.currentPage++;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentPage++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ____ __
|
||||||
|
* / __ \/ /___ ___ _____ __________
|
||||||
|
* / /_/ / / __ `/ / / / _ \/ ___/ ___/
|
||||||
|
* / ____/ / /_/ / /_/ / __/ / (__ )
|
||||||
|
* /_/ /_/\__,_/\__, /\___/_/ /____/
|
||||||
|
* /____/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles if the given player is should be an active player for the current game
|
||||||
|
* @param player The player to toggle the activity for
|
||||||
|
*/
|
||||||
|
togglePlayer(player: Player): void {
|
||||||
|
let index = this.actualPlayers.indexOf(player);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.actualPlayers.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
this.actualPlayers.push(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks, if the given player is currently marked as active for this game
|
||||||
|
* @param player The player to check the activity status for
|
||||||
|
* @returns boolean If the player is active
|
||||||
|
*/
|
||||||
|
isPlayerActive(player: Player): boolean {
|
||||||
|
return this.actualPlayers.indexOf(player) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns, if the amount of currently active players is greater or less than 4
|
||||||
|
*/
|
||||||
|
not4Players() {
|
||||||
|
return this.actualPlayers.length !== 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the names of the active players as a comma-separated string
|
||||||
|
*/
|
||||||
|
getPlayerNamesAsString(): string {
|
||||||
|
let playerNames = '';
|
||||||
|
for (let player of this.actualPlayers) {
|
||||||
|
playerNames += player.firstName + ', ';
|
||||||
|
}
|
||||||
|
// Remove last ", "
|
||||||
|
return playerNames.substring(0, playerNames.length - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ___ __
|
||||||
|
* / | ____ ____ ____ __ ______ ________ ____ ___ ___ ____ / /______
|
||||||
|
* / /| | / __ \/ __ \/ __ \/ / / / __ \/ ___/ _ \/ __ `__ \/ _ \/ __ \/ __/ ___/
|
||||||
|
* / ___ |/ / / / / / / /_/ / /_/ / / / / /__/ __/ / / / / / __/ / / / /_(__ )
|
||||||
|
* /_/ |_/_/ /_/_/ /_/\____/\__,_/_/ /_/\___/\___/_/ /_/ /_/\___/_/ /_/\__/____/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the activity status for the given announcement and also activates / deactivates other announcements
|
||||||
|
* to conform to the rules.
|
||||||
|
* @param announcement The announcement to toggle activity for
|
||||||
|
*/
|
||||||
|
toggleAnnouncement(announcement: announcements.Announcement): void {
|
||||||
|
let index = this.selectedAnnouncements.indexOf(announcement);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.selectedAnnouncements.splice(index, 1);
|
||||||
|
this.deactivateLowerAnnouncements(announcement);
|
||||||
|
} else {
|
||||||
|
this.selectedAnnouncements.push(announcement);
|
||||||
|
this.activateHigherAnnouncements(announcement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deactivates all lower announcements. E.g.: When RE is deactivated, RE_NO_NINETY and all lower also get deactivated
|
||||||
|
* @param announcement The announcement that has been deactivated
|
||||||
|
*/
|
||||||
|
deactivateLowerAnnouncements(announcement: announcements.Announcement) {
|
||||||
|
// First for RE
|
||||||
|
switch (announcement) {
|
||||||
|
case announcements.Announcement.RE:
|
||||||
|
this.deactivateAnnouncement(announcements.Announcement.RE_NO_NINETY);
|
||||||
|
this.deactivateAnnouncement(announcements.Announcement.RE_NO_SIXTY);
|
||||||
|
this.deactivateAnnouncement(announcements.Announcement.RE_NO_THIRTY);
|
||||||
|
break;
|
||||||
|
case announcements.Announcement.RE_NO_NINETY:
|
||||||
|
this.deactivateAnnouncement(announcements.Announcement.RE_NO_SIXTY);
|
||||||
|
this.deactivateAnnouncement(announcements.Announcement.RE_NO_THIRTY);
|
||||||
|
break;
|
||||||
|
case announcements.Announcement.RE_NO_SIXTY:
|
||||||
|
this.deactivateAnnouncement(announcements.Announcement.RE_NO_THIRTY);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now for CONTRA
|
||||||
|
switch (announcement) {
|
||||||
|
case announcements.Announcement.CONTRA:
|
||||||
|
this.deactivateAnnouncement(announcements.Announcement.CONTRA_NO_NINETY);
|
||||||
|
this.deactivateAnnouncement(announcements.Announcement.CONTRA_NO_SIXTY);
|
||||||
|
this.deactivateAnnouncement(announcements.Announcement.CONTRA_NO_THIRTY);
|
||||||
|
break;
|
||||||
|
case announcements.Announcement.CONTRA_NO_NINETY:
|
||||||
|
this.deactivateAnnouncement(announcements.Announcement.CONTRA_NO_SIXTY);
|
||||||
|
this.deactivateAnnouncement(announcements.Announcement.CONTRA_NO_THIRTY);
|
||||||
|
break;
|
||||||
|
case announcements.Announcement.CONTRA_NO_SIXTY:
|
||||||
|
this.deactivateAnnouncement(announcements.Announcement.CONTRA_NO_THIRTY);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deactivates the given announcement if it is active
|
||||||
|
* @param announcement The announcement to deactivate
|
||||||
|
*/
|
||||||
|
deactivateAnnouncement(announcement: announcements.Announcement) {
|
||||||
|
let index = this.selectedAnnouncements.indexOf(announcement);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.selectedAnnouncements.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activates all higher announcements. E.g.: When RE_NO_NINETY is activated, RE also gets activated
|
||||||
|
* @param announcement The announcement that has been activated
|
||||||
|
*/
|
||||||
|
activateHigherAnnouncements(announcement: announcements.Announcement) {
|
||||||
|
// First for RE
|
||||||
|
switch (announcement) {
|
||||||
|
case announcements.Announcement.RE_NO_THIRTY:
|
||||||
|
this.activateAnnouncement(announcements.Announcement.RE_NO_SIXTY);
|
||||||
|
this.activateAnnouncement(announcements.Announcement.RE_NO_NINETY);
|
||||||
|
this.activateAnnouncement(announcements.Announcement.RE);
|
||||||
|
break;
|
||||||
|
case announcements.Announcement.RE_NO_SIXTY:
|
||||||
|
this.activateAnnouncement(announcements.Announcement.RE_NO_NINETY);
|
||||||
|
this.activateAnnouncement(announcements.Announcement.RE);
|
||||||
|
break;
|
||||||
|
case announcements.Announcement.RE_NO_NINETY:
|
||||||
|
this.activateAnnouncement(announcements.Announcement.RE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now for CONTRA
|
||||||
|
switch (announcement) {
|
||||||
|
case announcements.Announcement.CONTRA_NO_THIRTY:
|
||||||
|
this.activateAnnouncement(announcements.Announcement.CONTRA_NO_SIXTY);
|
||||||
|
this.activateAnnouncement(announcements.Announcement.CONTRA_NO_NINETY);
|
||||||
|
this.activateAnnouncement(announcements.Announcement.CONTRA);
|
||||||
|
break;
|
||||||
|
case announcements.Announcement.CONTRA_NO_SIXTY:
|
||||||
|
this.activateAnnouncement(announcements.Announcement.CONTRA_NO_NINETY);
|
||||||
|
this.activateAnnouncement(announcements.Announcement.CONTRA);
|
||||||
|
break;
|
||||||
|
case announcements.Announcement.CONTRA_NO_NINETY:
|
||||||
|
this.activateAnnouncement(announcements.Announcement.CONTRA);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activates the given announcement if it is inactive
|
||||||
|
* @param announcement The announcement to activate
|
||||||
|
*/
|
||||||
|
activateAnnouncement(announcement: announcements.Announcement) {
|
||||||
|
let index = this.selectedAnnouncements.indexOf(announcement);
|
||||||
|
if (index === -1) {
|
||||||
|
this.selectedAnnouncements.push(announcement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given announcement is already marked as selected
|
||||||
|
* @param announcement The announcement to check the status for
|
||||||
|
*/
|
||||||
|
isAnnouncementActive(announcement: announcements.Announcement): boolean {
|
||||||
|
return this.selectedAnnouncements.indexOf(announcement) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of all possible announcements
|
||||||
|
*/
|
||||||
|
getAllPossibleAnnouncements(): announcements.Announcement[] {
|
||||||
|
return announcements.getAllAnnouncementValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks, if the currently selected announcements are a valid set of announcements
|
||||||
|
*/
|
||||||
|
checkAnnouncementsValid(): boolean {
|
||||||
|
return announcements.checkValidity(this.selectedAnnouncements);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the two highest announcements for this game (one for RE, one for CONTRA, if applicable)
|
||||||
|
*/
|
||||||
|
getHighestAnnouncements(): string {
|
||||||
|
return announcements.returnTwoHighestAnnouncements(this.selectedAnnouncements);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given announcement has been selected
|
||||||
|
* @param announcement The announcement to check
|
||||||
|
*/
|
||||||
|
checkAnnouncementActive(announcement: announcements.Announcement): boolean {
|
||||||
|
let index = this.selectedAnnouncements.indexOf(announcement);
|
||||||
|
return index !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ______
|
||||||
|
* /_ __/__ ____ _____ ___ _____
|
||||||
|
* / / / _ \/ __ `/ __ `__ \/ ___/
|
||||||
|
* / / / __/ /_/ / / / / / (__ )
|
||||||
|
* /_/ \___/\__,_/_/ /_/ /_/____/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the players team
|
||||||
|
* @param player The player to toggle the team for
|
||||||
|
*/
|
||||||
|
toggleElderPlayer(player: Player): void {
|
||||||
|
if (player.team === Team.RE) {
|
||||||
|
player.team = Team.CONTRA;
|
||||||
|
} else {
|
||||||
|
player.team = Team.RE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the player is an elder
|
||||||
|
* @param player The player to check
|
||||||
|
*/
|
||||||
|
isPlayerElder(player: Player): boolean {
|
||||||
|
return player.team === Team.RE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current team assignment is valid
|
||||||
|
*/
|
||||||
|
checkValidTeamAssignment(): boolean {
|
||||||
|
let numberOfElderPlayers: number = 0;
|
||||||
|
for (let player of this.actualPlayers) {
|
||||||
|
if (player.team === Team.RE) {
|
||||||
|
numberOfElderPlayers++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !(numberOfElderPlayers !== 1 && numberOfElderPlayers !== 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _____
|
||||||
|
* / ___/_________ ________ _____
|
||||||
|
* \__ \/ ___/ __ \/ ___/ _ \/ ___/
|
||||||
|
* ___/ / /__/ /_/ / / / __(__ )
|
||||||
|
* /____/\___/\____/_/ \___/____/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the sum of all points is exactly 240
|
||||||
|
*/
|
||||||
|
calculatePointSum(): number {
|
||||||
|
let totalScore: number = 0;
|
||||||
|
for (let player of this.actualPlayers) {
|
||||||
|
totalScore += player.finalCardScore ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a string that explains if the total point sum is too low / too high
|
||||||
|
*/
|
||||||
|
getScoreDifferenceText(): string {
|
||||||
|
let difference = this.calculatePointSum() - 240;
|
||||||
|
|
||||||
|
if (difference > 0) {
|
||||||
|
return difference + ' more than expected';
|
||||||
|
} else {
|
||||||
|
difference *= -1;
|
||||||
|
return difference + ' less than expected';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ______ _____
|
||||||
|
* / ____/___ _____ ___ ___ / ___/_________ ________
|
||||||
|
* / / __/ __ `/ __ `__ \/ _ \ \__ \/ ___/ __ \/ ___/ _ \
|
||||||
|
* / /_/ / /_/ / / / / / / __/ ___/ / /__/ /_/ / / / __/
|
||||||
|
* \____/\__,_/_/ /_/ /_/\___/ /____/\___/\____/_/ \___/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the game points for all players
|
||||||
|
*/
|
||||||
|
calculateCurrentScores(): void {
|
||||||
|
let gameScore: number = 0;
|
||||||
|
|
||||||
|
let winningTeam = this.getWinningTeamAndScore().team;
|
||||||
|
let winningTeamScore = this.getWinningTeamAndScore().score;
|
||||||
|
let unfulfilledAnnouncementPoints = this.checkUnfulfilledAnnouncements(winningTeam, winningTeamScore);
|
||||||
|
let isSoloPlay = this.checkIfSolo();
|
||||||
|
|
||||||
|
// We are going to calculate the points for the winning team and then set all players points accordingly
|
||||||
|
|
||||||
|
if (unfulfilledAnnouncementPoints === 0) {
|
||||||
|
gameScore += this.calculateNormalScore(winningTeamScore, winningTeam);
|
||||||
|
} else {
|
||||||
|
gameScore += unfulfilledAnnouncementPoints;
|
||||||
|
winningTeam = winningTeam === Team.RE ? Team.CONTRA : Team.RE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(winningTeam === Team.CONTRA) {
|
||||||
|
// Against the elders
|
||||||
|
gameScore++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Double Score in case of announcement
|
||||||
|
if (this.checkAnnouncementActive(announcements.Announcement.RE)) {
|
||||||
|
gameScore *= 2;
|
||||||
|
}
|
||||||
|
if (this.checkAnnouncementActive(announcements.Announcement.CONTRA)) {
|
||||||
|
gameScore *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bonus points
|
||||||
|
if (!isSoloPlay) {
|
||||||
|
gameScore += this.getFinalFoxPoints(winningTeam);
|
||||||
|
gameScore += this.getCharliePoints(winningTeam);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setGameScores(gameScore, winningTeam, isSoloPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the score according to card points and announcements in case of a "normal" game, so without unfulfilled announcements
|
||||||
|
* @param winningTeamScore
|
||||||
|
* @param winningTeam
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private calculateNormalScore(winningTeamScore: number, winningTeam: Team): number {
|
||||||
|
let gameScore = 1; // 1 Point for Winning
|
||||||
|
|
||||||
|
// Won by how much?
|
||||||
|
if (winningTeamScore > 210) {
|
||||||
|
gameScore += 3;
|
||||||
|
} else if (winningTeamScore > 180) {
|
||||||
|
gameScore += 2;
|
||||||
|
} else if (winningTeamScore > 150) {
|
||||||
|
gameScore += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Announcements
|
||||||
|
if (winningTeam === Team.RE) {
|
||||||
|
if (this.checkAnnouncementActive(announcements.Announcement.RE_NO_NINETY) && winningTeamScore > 150) {
|
||||||
|
gameScore++;
|
||||||
|
}
|
||||||
|
if (this.checkAnnouncementActive(announcements.Announcement.RE_NO_SIXTY) && winningTeamScore > 180) {
|
||||||
|
gameScore++;
|
||||||
|
}
|
||||||
|
if (this.checkAnnouncementActive(announcements.Announcement.RE_NO_THIRTY) && winningTeamScore > 210) {
|
||||||
|
gameScore++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (winningTeam === Team.CONTRA) {
|
||||||
|
if (this.checkAnnouncementActive(announcements.Announcement.CONTRA_NO_NINETY) && winningTeamScore > 150) {
|
||||||
|
gameScore++;
|
||||||
|
}
|
||||||
|
if (this.checkAnnouncementActive(announcements.Announcement.CONTRA_NO_SIXTY) && winningTeamScore > 180) {
|
||||||
|
gameScore++;
|
||||||
|
}
|
||||||
|
if (this.checkAnnouncementActive(announcements.Announcement.CONTRA_NO_THIRTY) && winningTeamScore > 210) {
|
||||||
|
gameScore++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gameScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the team card score of the given players team
|
||||||
|
* @param player The player to calculate the teams points for
|
||||||
|
*/
|
||||||
|
getTeamScore(player: Player): number {
|
||||||
|
let totalTeamScore: number = player.finalCardScore ?? 0;
|
||||||
|
|
||||||
|
for (let otherPlayer of this.actualPlayers) {
|
||||||
|
if (otherPlayer !== player && otherPlayer.team === player.team) {
|
||||||
|
totalTeamScore += otherPlayer.finalCardScore ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalTeamScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates which team won and returns the Team Name and the achieved score
|
||||||
|
*/
|
||||||
|
getWinningTeamAndScore(): { team: Team, score: number } {
|
||||||
|
let elderPoints: number = 0;
|
||||||
|
let otherPoints: number = 0;
|
||||||
|
|
||||||
|
for (let player of this.actualPlayers) {
|
||||||
|
if (player.team === Team.RE) {
|
||||||
|
elderPoints += player.finalCardScore ?? 0;
|
||||||
|
} else {
|
||||||
|
otherPoints += player.finalCardScore ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elderPoints > otherPoints) {
|
||||||
|
return {
|
||||||
|
team: Team.RE,
|
||||||
|
score: elderPoints
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
team: Team.CONTRA,
|
||||||
|
score: otherPoints
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the game scores for all players
|
||||||
|
* @param score The score to set
|
||||||
|
* @param winningTeam The team that won
|
||||||
|
*/
|
||||||
|
setGameScores(score: number, winningTeam: Team, isSolo: boolean) {
|
||||||
|
for (let player of this.actualPlayers) {
|
||||||
|
if (player.team === winningTeam) {
|
||||||
|
if(isSolo) {
|
||||||
|
player.gamePoints = score * 3;
|
||||||
|
} else {
|
||||||
|
player.gamePoints = score;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
player.gamePoints = -score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the points that each team got by catching foxes
|
||||||
|
*/
|
||||||
|
calculateFoxPoints(): { re: number, contra: number } {
|
||||||
|
let reFoxesCaught: number = 0;
|
||||||
|
let contraFoxesCaught: number = 0;
|
||||||
|
|
||||||
|
for (let player of this.actualPlayers) {
|
||||||
|
if (player.team === Team.RE) {
|
||||||
|
reFoxesCaught += player.foxesCaught ?? 0;
|
||||||
|
} else {
|
||||||
|
contraFoxesCaught += player.foxesCaught ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
re: reFoxesCaught,
|
||||||
|
contra: contraFoxesCaught
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the final fox points for the winning team
|
||||||
|
* @param winningTeam The winning team
|
||||||
|
*/
|
||||||
|
getFinalFoxPoints(winningTeam: Team): number {
|
||||||
|
let finalPoints = 0;
|
||||||
|
|
||||||
|
if (winningTeam === Team.RE) {
|
||||||
|
finalPoints += this.calculateFoxPoints().re;
|
||||||
|
finalPoints -= this.calculateFoxPoints().contra;
|
||||||
|
} else {
|
||||||
|
finalPoints += this.calculateFoxPoints().contra;
|
||||||
|
finalPoints -= this.calculateFoxPoints().re;
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the extra points for a when a charlie made the final trick.
|
||||||
|
* If the winning team played the charlie, they get one point. If the losing team played the charlie, the winning team loses one point.
|
||||||
|
* @param winningTeam The winning team
|
||||||
|
*/
|
||||||
|
getCharliePoints(winningTeam: Team): number {
|
||||||
|
for (let player of this.actualPlayers) {
|
||||||
|
if (player.wonLastTrickWithCharlie) {
|
||||||
|
return player.team === winningTeam ? 1 : -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the winning team has made announcements that have not been fulfilled.
|
||||||
|
* If so, returns the points that the "losing" team gets for these unfulfilled announcements
|
||||||
|
* @param normalWinningTeam The team that would have won under normal circumstances
|
||||||
|
* @param normalWinningTeamScore The card score of said team
|
||||||
|
*/
|
||||||
|
checkUnfulfilledAnnouncements(normalWinningTeam: Team, normalWinningTeamScore: number): number {
|
||||||
|
let gamePoints = 0;
|
||||||
|
|
||||||
|
if (normalWinningTeam === Team.RE) {
|
||||||
|
if (this.checkAnnouncementActive(announcements.Announcement.RE_NO_NINETY) && normalWinningTeamScore < 151) {
|
||||||
|
gamePoints++;
|
||||||
|
}
|
||||||
|
if (this.checkAnnouncementActive(announcements.Announcement.RE_NO_SIXTY) && normalWinningTeamScore < 181) {
|
||||||
|
gamePoints++;
|
||||||
|
}
|
||||||
|
if (this.checkAnnouncementActive(announcements.Announcement.RE_NO_THIRTY) && normalWinningTeamScore < 211) {
|
||||||
|
gamePoints++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (normalWinningTeam === Team.CONTRA) {
|
||||||
|
if (this.checkAnnouncementActive(announcements.Announcement.CONTRA_NO_NINETY) && normalWinningTeamScore < 151) {
|
||||||
|
gamePoints++;
|
||||||
|
}
|
||||||
|
if (this.checkAnnouncementActive(announcements.Announcement.CONTRA_NO_SIXTY) && normalWinningTeamScore < 181) {
|
||||||
|
gamePoints++;
|
||||||
|
}
|
||||||
|
if (this.checkAnnouncementActive(announcements.Announcement.CONTRA_NO_THIRTY) && normalWinningTeamScore < 211) {
|
||||||
|
gamePoints++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gamePoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _____ __
|
||||||
|
* / ___/____ / /___
|
||||||
|
* \__ \/ __ \/ / __ \
|
||||||
|
* ___/ / /_/ / / /_/ /
|
||||||
|
* /____/\____/_/\____/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks, according to the assigned Teams, if this is a solo play
|
||||||
|
*/
|
||||||
|
checkIfSolo(): boolean {
|
||||||
|
let numberOfElders: number = 0;
|
||||||
|
|
||||||
|
for(let player of this.actualPlayers) {
|
||||||
|
if(player.team === Team.RE) {
|
||||||
|
numberOfElders++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return numberOfElders === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given solo is currently active
|
||||||
|
* @param solo The solo to check active status for
|
||||||
|
*/
|
||||||
|
isSoloActive(solo: solos.Solo): boolean {
|
||||||
|
return this.soloPlayed === solo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all possible solo values
|
||||||
|
*/
|
||||||
|
getAllPossibleSolos(): solos.Solo[] {
|
||||||
|
return solos.getAllSoloValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the given solo as the active one
|
||||||
|
* @param solo The solo to set as the active one
|
||||||
|
*/
|
||||||
|
toggleSolo(solo: solos.Solo): void {
|
||||||
|
this.soloPlayed = solo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there is a solo selected, false otherwise
|
||||||
|
*/
|
||||||
|
noSoloSelectedYet(): boolean {
|
||||||
|
return this.soloPlayed === undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ______ __ ____ _ __
|
||||||
|
* / ____/ __/ /__________ _ / __ \____ (_)___ / /______
|
||||||
|
* / __/ | |/_/ __/ ___/ __ `/ / /_/ / __ \/ / __ \/ __/ ___/
|
||||||
|
* / /____> </ /_/ / / /_/ / / ____/ /_/ / / / / / /_(__ )
|
||||||
|
* /_____/_/|_|\__/_/ \__,_/ /_/ \____/_/_/ /_/\__/____/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the number of caught foxes checks out
|
||||||
|
*/
|
||||||
|
checkTotalFoxPoints(): boolean {
|
||||||
|
let totalFoxPoints: number = 0;
|
||||||
|
|
||||||
|
for (let player of this.actualPlayers) {
|
||||||
|
totalFoxPoints += player.foxesCaught ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalFoxPoints <= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if all extra point types have valid entries
|
||||||
|
*/
|
||||||
|
checkAllExtraPoints(): boolean {
|
||||||
|
return this.checkTotalFoxPoints();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _____
|
||||||
|
* / ___/__ ______ ___ ____ ___ ____ ________ __
|
||||||
|
* \__ \/ / / / __ `__ \/ __ `__ \/ __ `/ ___/ / / /
|
||||||
|
* ___/ / /_/ / / / / / / / / / / / /_/ / / / /_/ /
|
||||||
|
* /____/\__,_/_/ /_/ /_/_/ /_/ /_/\__,_/_/ \__, /
|
||||||
|
* /____/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the game stats to the API to be saved
|
||||||
|
*/
|
||||||
|
saveGame(): void {
|
||||||
|
//TODO implement
|
||||||
|
// call api method, then return to gameNight page / close modal or sth like that
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,27 @@
|
|||||||
<div id="header">
|
<div id="header">
|
||||||
<p id="header-title">Doppelkopf-Stats</p>
|
<p id="header-title" (click)="navigate('/')">Doppelkopf-Stats</p>
|
||||||
<span id="menu">
|
<span id="menu">
|
||||||
<div id="menu-desktop">
|
<div id="menu-desktop">
|
||||||
<div>Home</div>
|
<div (click)="navigate('/')">Home</div>
|
||||||
<div>Game night</div>
|
<div (click)="navigate('/gamenight')">Game night</div>
|
||||||
<div>Stats</div>
|
<div (click)="navigate('match-history')">Match History</div>
|
||||||
|
<div (click)="navigate('/stats')">Stats</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="menu-mobile">
|
<div id="menu-mobile">
|
||||||
<div>Open</div>
|
<div id="menu-mobile-dropdown-button" (click)="openMobileDropdown()">Open</div>
|
||||||
|
<div id="menu-mobile-dropdown-content">
|
||||||
|
<div (click)="navigate('/')">Home</div>
|
||||||
|
<div (click)="navigate('/gamenight')">Game night</div>
|
||||||
|
<div (click)="navigate('match-history')">Match History</div>
|
||||||
|
<div (click)="navigate('/stats')">Stats</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
<span id="user-info">
|
<span id="user-info">
|
||||||
<p>{{getUserName()}}</p>
|
<p>{{getUserName()}}</p>
|
||||||
<img src="assets/images/user.png" alt="User Icon" (click)="openProfile()">
|
<img src="assets/images/user.png" alt="User Icon" (click)="openProfile()">
|
||||||
</span>
|
</span>
|
||||||
<app-profile (showProfilePopOverChange)="closeProfile()" *ngIf="showProfilePopOver" [user]="this.user"></app-profile>
|
<app-profile (showProfilePopOverChange)="closeProfile()" *ngIf="showProfilePopOver"
|
||||||
|
[user]="this.user"></app-profile>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -43,12 +43,14 @@
|
|||||||
display: inherit;
|
display: inherit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(max-width: 767px) {
|
@media(max-width: 767px) {
|
||||||
#menu-desktop {
|
#menu-desktop {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
#menu-mobile {
|
#menu-mobile {
|
||||||
display: inherit;
|
display: inherit;
|
||||||
|
width: 10em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,9 +60,41 @@
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#menu div div {
|
#menu div#menu-desktop div {
|
||||||
padding-left: 2em;
|
padding-left: 2em;
|
||||||
padding-right: 2em;
|
padding-right: 2em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu-desktop div:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
color: $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dropdown Menu Mobile */
|
||||||
|
|
||||||
|
#menu-mobile-dropdown-content {
|
||||||
|
display: none;
|
||||||
|
z-index: 2;
|
||||||
|
position: absolute;
|
||||||
|
top: $header_height + 0.3em;
|
||||||
|
padding-left: 2em;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu-mobile-dropdown-content div {
|
||||||
|
color: #F2F1E8;
|
||||||
|
border: solid 1px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0.3em;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu-mobile-dropdown-button{
|
||||||
|
padding: 10px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, HostListener, OnInit} from '@angular/core';
|
||||||
import {User} from '../../models/user';
|
import {User} from '../../models/user';
|
||||||
import {StorageService} from '../../services/storage.service';
|
import {StorageService} from '../../services/storage.service';
|
||||||
|
import {Router} from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-header',
|
selector: 'app-header',
|
||||||
@@ -11,12 +12,15 @@ export class HeaderComponent implements OnInit {
|
|||||||
|
|
||||||
showProfilePopOver: boolean = false;
|
showProfilePopOver: boolean = false;
|
||||||
user?: User;
|
user?: User;
|
||||||
|
initialClickMobileMenu : boolean = true
|
||||||
|
dropdownContentStyle! : CSSStyleDeclaration;
|
||||||
|
|
||||||
constructor() {
|
constructor(private router: Router) {
|
||||||
this.user = StorageService.getUserInfo();
|
this.user = StorageService.getUserInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.dropdownContentStyle = document.getElementById('menu-mobile-dropdown-content')!.style;
|
||||||
}
|
}
|
||||||
|
|
||||||
openProfile(): void {
|
openProfile(): void {
|
||||||
@@ -27,7 +31,46 @@ export class HeaderComponent implements OnInit {
|
|||||||
this.showProfilePopOver = false;
|
this.showProfilePopOver = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
getUserName(): string {
|
getUserName(): string {
|
||||||
return this.user ? this.user.firstName : 'Logged out';
|
return this.user ? this.user.firstName : 'Logged out';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
navigate(path: string): void {
|
||||||
|
this.initialClickMobileMenu = true;
|
||||||
|
this.router.navigate([path]);
|
||||||
|
}
|
||||||
|
|
||||||
|
openMobileDropdown() : void {
|
||||||
|
if (!this.isMobileDropdownOpen()) {
|
||||||
|
this.dropdownContentStyle.display = 'block';
|
||||||
|
this.initialClickMobileMenu = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closeMobileDropdown() : void {
|
||||||
|
this.dropdownContentStyle.display = 'none';
|
||||||
|
this.initialClickMobileMenu = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
isMobileDropdownOpen() : Boolean {
|
||||||
|
return this.dropdownContentStyle.display == 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('document:click', ['$event'])
|
||||||
|
@HostListener('document:touchstart', ['$event'])
|
||||||
|
handleOutsideClick(event: any) {
|
||||||
|
if (!document.getElementById("menu-mobile-dropdown-content")!.contains(event.target)) {
|
||||||
|
if(this.isMobileDropdownOpen()) {
|
||||||
|
if(this.initialClickMobileMenu) {
|
||||||
|
this.initialClickMobileMenu = false;
|
||||||
|
} else {
|
||||||
|
this.closeMobileDropdown();
|
||||||
|
console.log("Zu gemacht!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,4 +35,5 @@ export class ProfileComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Close modal also when "escape" is clicked
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,114 @@
|
|||||||
export enum Announcement {
|
export enum Announcement {
|
||||||
RE = 0,
|
RE = 'Re',
|
||||||
KONTRA = 1,
|
CONTRA = 'Contra',
|
||||||
NO_NINETY = 2,
|
RE_NO_NINETY = 'Re: No ninety',
|
||||||
NO_SIXTY = 3,
|
CONTRA_NO_NINETY = 'Contra: No ninety',
|
||||||
NO_THIRTY = 4
|
RE_NO_SIXTY = 'Re: No sixty',
|
||||||
|
CONTRA_NO_SIXTY = 'Contra: No sixty',
|
||||||
|
RE_NO_THIRTY = 'Re: No thirty',
|
||||||
|
CONTRA_NO_THIRTY = 'Contra: No thirty'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all available announcement values
|
||||||
|
*/
|
||||||
|
export function getAllAnnouncementValues(): Announcement[] {
|
||||||
|
return [
|
||||||
|
Announcement.RE,
|
||||||
|
Announcement.RE_NO_NINETY,
|
||||||
|
Announcement.RE_NO_SIXTY,
|
||||||
|
Announcement.RE_NO_THIRTY,
|
||||||
|
Announcement.CONTRA,
|
||||||
|
Announcement.CONTRA_NO_NINETY,
|
||||||
|
Announcement.CONTRA_NO_SIXTY,
|
||||||
|
Announcement.CONTRA_NO_THIRTY
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the selected announcements are a valid set of announcements.
|
||||||
|
* E.g.: If RE_NO_NINETY is newly selected, RE also has to be selected
|
||||||
|
* @param selectedAnnouncements The list of selected announcements
|
||||||
|
*/
|
||||||
|
export function checkValidity(selectedAnnouncements: Announcement[]): boolean {
|
||||||
|
// First check all "RE" Announcements
|
||||||
|
if(selectedAnnouncements.indexOf(Announcement.RE_NO_THIRTY) !== -1) {
|
||||||
|
if(selectedAnnouncements.indexOf(Announcement.RE_NO_SIXTY) === -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(selectedAnnouncements.indexOf(Announcement.RE_NO_SIXTY) !== -1) {
|
||||||
|
if(selectedAnnouncements.indexOf(Announcement.RE_NO_NINETY) === -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(selectedAnnouncements.indexOf(Announcement.RE_NO_NINETY) !== -1) {
|
||||||
|
if(selectedAnnouncements.indexOf(Announcement.RE) === -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now same for "CONTRA"
|
||||||
|
if(selectedAnnouncements.indexOf(Announcement.CONTRA_NO_THIRTY) !== -1) {
|
||||||
|
if(selectedAnnouncements.indexOf(Announcement.CONTRA_NO_SIXTY) === -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(selectedAnnouncements.indexOf(Announcement.CONTRA_NO_SIXTY) !== -1) {
|
||||||
|
if(selectedAnnouncements.indexOf(Announcement.CONTRA_NO_NINETY) === -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(selectedAnnouncements.indexOf(Announcement.CONTRA_NO_NINETY) !== -1) {
|
||||||
|
if(selectedAnnouncements.indexOf(Announcement.CONTRA) === -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// all fine, return true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the names of the two highest announcements from the given list, one for RE and one for CONTRA if applicable
|
||||||
|
* @param selectedAnnouncements The list of announcements to check
|
||||||
|
*/
|
||||||
|
export function returnTwoHighestAnnouncements(selectedAnnouncements: Announcement[]): string {
|
||||||
|
let finalString: string = '';
|
||||||
|
|
||||||
|
// First check "RE" announcements
|
||||||
|
if(selectedAnnouncements.indexOf(Announcement.RE_NO_THIRTY) !== -1) {
|
||||||
|
finalString += Announcement.RE_NO_THIRTY;
|
||||||
|
} else if (selectedAnnouncements.indexOf(Announcement.RE_NO_SIXTY) !== -1) {
|
||||||
|
finalString += Announcement.RE_NO_SIXTY;
|
||||||
|
} else if (selectedAnnouncements.indexOf(Announcement.RE_NO_NINETY) !== -1) {
|
||||||
|
finalString += Announcement.RE_NO_NINETY;
|
||||||
|
} else if (selectedAnnouncements.indexOf(Announcement.RE) !== -1) {
|
||||||
|
finalString += Announcement.RE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there was a "RE" announcement, add a ", " so we can list the CONTRA announcement properly
|
||||||
|
if(finalString !== '') {
|
||||||
|
finalString += ', ';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now check "CONTRA"
|
||||||
|
if(selectedAnnouncements.indexOf(Announcement.CONTRA_NO_THIRTY) !== -1) {
|
||||||
|
finalString += Announcement.CONTRA_NO_THIRTY;
|
||||||
|
} else if (selectedAnnouncements.indexOf(Announcement.CONTRA_NO_SIXTY) !== -1) {
|
||||||
|
finalString += Announcement.CONTRA_NO_SIXTY;
|
||||||
|
} else if (selectedAnnouncements.indexOf(Announcement.CONTRA_NO_NINETY) !== -1) {
|
||||||
|
finalString += Announcement.CONTRA_NO_NINETY;
|
||||||
|
} else if (selectedAnnouncements.indexOf(Announcement.CONTRA) !== -1) {
|
||||||
|
finalString += Announcement.CONTRA;
|
||||||
|
} else {
|
||||||
|
// Remove the last two chars from the finalString (", ")
|
||||||
|
finalString = finalString.substring(0, finalString.length-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(finalString === '') {
|
||||||
|
finalString = 'None';
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalString;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,24 @@
|
|||||||
export enum Solo {
|
export enum Solo {
|
||||||
QUEENS = 0,
|
QUEENS = 'Queens',
|
||||||
JACKS = 1,
|
JACKS = 'Jacks',
|
||||||
COLOR_CHECKS = 2,
|
COLOR_CHECKS = 'Checks',
|
||||||
COLOR_HEART = 3,
|
COLOR_HEART = 'Heart',
|
||||||
COLOR_SPADES = 4,
|
COLOR_SPADES = 'Spades',
|
||||||
COLOR_CROSS = 5,
|
COLOR_CROSS = 'Cross',
|
||||||
FLESHLESS = 6
|
FLESHLESS = 'Fleshless'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all available solo values
|
||||||
|
*/
|
||||||
|
export function getAllSoloValues(): Solo[] {
|
||||||
|
return [
|
||||||
|
Solo.QUEENS,
|
||||||
|
Solo.JACKS,
|
||||||
|
Solo.COLOR_CHECKS,
|
||||||
|
Solo.COLOR_HEART,
|
||||||
|
Solo.COLOR_SPADES,
|
||||||
|
Solo.COLOR_CROSS,
|
||||||
|
Solo.FLESHLESS
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export enum Team {
|
export enum Team {
|
||||||
RE = 0,
|
RE = 'Re',
|
||||||
KONTRA = 1
|
CONTRA = 'Contra'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import {Solo} from './enums/solo';
|
|||||||
export interface Game {
|
export interface Game {
|
||||||
gameId: number;
|
gameId: number;
|
||||||
players: Player[];
|
players: Player[];
|
||||||
foxesCaught: number;
|
|
||||||
announcements: Announcement[];
|
|
||||||
solo?: Solo;
|
solo?: Solo;
|
||||||
againstTheElders: boolean;
|
againstTheElders: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ export interface Player {
|
|||||||
firstName: string;
|
firstName: string;
|
||||||
finalCardScore: number;
|
finalCardScore: number;
|
||||||
gamePoints: number;
|
gamePoints: number;
|
||||||
hadMarriage: boolean;
|
hadMarriage?: boolean;
|
||||||
hadTrumpHandoff: boolean;
|
hadTrumpHandoff?: boolean;
|
||||||
team: Team;
|
team?: Team;
|
||||||
|
foxesCaught?: number;
|
||||||
|
wonLastTrickWithCharlie?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,3 +2,4 @@
|
|||||||
<div id="gamenight">
|
<div id="gamenight">
|
||||||
<p>gamenight works!</p>
|
<p>gamenight works!</p>
|
||||||
</div>
|
</div>
|
||||||
|
<app-add-game></app-add-game>
|
||||||
|
|||||||
@@ -17,8 +17,20 @@ export class HomeComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
// TODO: First try to read existing session data
|
if(!HomeComponent.sessionDataAvailable()) {
|
||||||
this.authenticateUser();
|
this.authenticateUser();
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if there is session data saved in the local storage
|
||||||
|
* @return If there is session data available
|
||||||
|
*/
|
||||||
|
private static sessionDataAvailable(): boolean {
|
||||||
|
let user = StorageService.getUserInfo();
|
||||||
|
|
||||||
|
return user !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,6 +47,10 @@ export class HomeComponent implements OnInit {
|
|||||||
sessionKey: ''
|
sessionKey: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if(user.firebonkId === undefined || user.firstName === undefined || user.token === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let authenticatedUser = ApiService.performAuthentication(user);
|
let authenticatedUser = ApiService.performAuthentication(user);
|
||||||
StorageService.setUserInfo(authenticatedUser);
|
StorageService.setUserInfo(authenticatedUser);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<app-header></app-header>
|
||||||
|
<div id="match-history">
|
||||||
|
<p>match-history works!</p>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
@import '../../../styles';
|
||||||
|
|
||||||
|
#match-history {
|
||||||
|
padding-top: $header_height;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { MatchHistoryComponent } from './match-history.component';
|
||||||
|
|
||||||
|
describe('MatchHistoryComponent', () => {
|
||||||
|
let component: MatchHistoryComponent;
|
||||||
|
let fixture: ComponentFixture<MatchHistoryComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ MatchHistoryComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(MatchHistoryComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-match-history',
|
||||||
|
templateUrl: './match-history.component.html',
|
||||||
|
styleUrls: ['./match-history.component.scss']
|
||||||
|
})
|
||||||
|
export class MatchHistoryComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+5
-1
@@ -3,13 +3,17 @@ $primary: #050533;
|
|||||||
$secondary: #0D698B;
|
$secondary: #0D698B;
|
||||||
$text: #F2F1E8;
|
$text: #F2F1E8;
|
||||||
$accent: #E34234;
|
$accent: #E34234;
|
||||||
|
$active: green;
|
||||||
|
$inactive: red;
|
||||||
|
$warn: orange;
|
||||||
|
$button: #2b2b2b;
|
||||||
|
|
||||||
/* Global vars */
|
/* Global vars */
|
||||||
$header_height: 3em;
|
$header_height: 3em;
|
||||||
|
|
||||||
/* Global defaults */
|
/* Global defaults */
|
||||||
html, body {
|
html, body {
|
||||||
height: 100%;
|
height: 95%;
|
||||||
background-color: $primary;
|
background-color: $primary;
|
||||||
color: $text;
|
color: $text;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
|
|||||||
Reference in New Issue
Block a user