Compare commits

..

35 Commits

Author SHA1 Message Date
Paddy 8d6129590a Fixing user info reading at page load
Jenkins Production Deployment
2022-09-26 17:59:36 +02:00
Paddy 13aa2c29b0 Fix bug where summary page was displayed together with extra points page 2022-09-26 17:45:17 +02:00
Paddy 5f256e1983 Adding first unit test for score calculation
Jenkins Production Deployment
2022-09-16 16:43:37 +02:00
Paddy 4dbc6a2a4d Adding point for winning against the elders
Jenkins Production Deployment
2022-09-16 15:28:01 +02:00
Paddy e26cd21cc6 Adding "game summary" page to add game component
Jenkins Production Deployment
2022-09-16 13:59:22 +02:00
Paddy 139fb20814 Adding comments to solo functions, prevent continuing process without selected solo
Jenkins Production Deployment
2022-09-16 12:33:18 +02:00
Paddy 1027011447 Adding solo page to add game component
Jenkins Production Deployment
2022-09-16 12:22:32 +02:00
Paddy b09b683727 Smol refactor
Jenkins Production Deployment
2022-09-16 11:50:39 +02:00
Paddy aec84dca5d Adding correct point calculation for Solo games
Jenkins Production Deployment
2022-09-16 11:49:39 +02:00
Paddy 7082abcdf7 Adding point calculation for unfulfilled announcements 2022-09-16 11:38:37 +02:00
Paddy 7c3404acb0 Refactor point calculation so we can add an additional method in case of unfulfilled annoumcements
Jenkins Production Deployment
2022-09-16 10:01:50 +02:00
Paddy e8165727de Add charlie to point calculation
Jenkins Production Deployment
2022-09-15 15:43:29 +02:00
Paddy 6d2c750a58 Adding fox points to calculation
Jenkins Production Deployment
2022-09-15 13:41:08 +02:00
Paddy 7f436ae0b8 Remove old console.logs, add TODOs 2022-09-14 22:36:58 +02:00
Paddy 753e490c29 Refactoring score calculation logic 2022-09-14 21:58:31 +02:00
Paddy 051657ef65 Reordering announcements 2022-09-14 19:38:11 +02:00
Paddy e41a07411e Adding advanced announcement selection logic
Jenkins Production Deployment
2022-09-14 19:00:23 +02:00
Paddy cd5154a07a Add text that tells the players exactly why the total points are wrong 2022-09-10 22:36:21 +02:00
Paddy ae45cc78a1 Fix bug in player selection screen
Jenkins Production Deployment
2022-09-10 22:24:59 +02:00
Julian d0df6e3e5c Merge remote-tracking branch 'origin/master' into master 2022-09-10 22:13:56 +02:00
Julian 5c675782aa Add close on outsideclick functionality to the mobile menu on header component. 2022-09-10 22:13:40 +02:00
Paddy 68e35fc5bc Added "card scores" page to "add game" component, startet on score calculation 2022-09-10 20:41:18 +02:00
Paddy a9aca1351c Some button styling 2022-09-10 19:33:55 +02:00
Paddy bed8992ddd Add "team selection" page to "add game" component 2022-09-10 19:26:12 +02:00
Paddy f69f7f1731 add-game.component.ts code cleanup 2022-09-10 18:45:22 +02:00
Paddy c6302ad8c0 Add "Announcements" page to "Add game" component 2022-09-10 18:38:55 +02:00
Paddy 14696fc116 Forgot to add this to the last commit 2022-09-10 14:54:53 +02:00
Paddy 5449cd7714 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	src/app/app.module.ts
2022-09-10 13:39:23 +02:00
Paddy 0e6d1b4390 WIP: Add Game Component
- Create Component
- Add logic to switch "pages" in component
- add "select player" logic
2022-09-10 13:38:36 +02:00
Julian 080fa80f03 Add Match History component and update header menu. 2022-09-09 21:50:11 +02:00
Julian 9c3547f712 Fix dropdown menu location (mobile menu). 2022-09-09 03:28:23 +02:00
Paddy b300fe1b7b Formatting, adding comments 2022-09-08 17:41:10 +02:00
Julian 0889c101aa Add links in desktop menu and dropdown menu in mobile (wip) 2022-09-08 17:04:48 +02:00
Paddy d0c485c0e9 WIP: Responsive header menu 2 2022-09-08 16:10:05 +02:00
Paddy 0c10e46d1a WIP: Responsive header menu 2022-09-08 15:01:02 +02:00
26 changed files with 1325 additions and 11993 deletions
+32 -11958
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -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({
-1
View File
@@ -12,6 +12,5 @@ export class AppComponent {
user?: Player; user?: Player;
ngOnInit() { ngOnInit() {
// Load player data
} }
} }
+8 -2
View File
@@ -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,8 +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">
<div id="menu-desktop">
<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 id="menu-mobile">
<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>
</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>
@@ -34,3 +34,67 @@
#user-info img:hover { #user-info img:hover {
cursor: pointer; cursor: pointer;
} }
@media (min-width: 768px) {
#menu-mobile {
display: none;
}
#menu-desktop {
display: inherit;
}
}
@media(max-width: 767px) {
#menu-desktop {
display: none;
}
#menu-mobile {
display: inherit;
width: 10em;
}
}
#menu {
display: flex;
justify-content: center;
max-width: 100%;
height: 100%;
align-items: center;
}
#menu div#menu-desktop div {
padding-left: 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;
}
+48 -5
View File
@@ -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',
@@ -9,25 +10,67 @@ import {StorageService} from '../../services/storage.service';
}) })
export class HeaderComponent implements OnInit { 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 {
this.showProfilePopover = true; this.showProfilePopOver = true;
} }
closeProfile(): void { closeProfile(): void {
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!")
}
}
}
}
} }
@@ -10,7 +10,7 @@ export class ProfileComponent implements OnInit {
initialClick = true; initialClick = true;
@Input() user?: User; @Input() user?: User;
@Output() showProfilePopoverChange = new EventEmitter<boolean>(); @Output() showProfilePopOverChange = new EventEmitter<boolean>();
constructor(private eRef: ElementRef) { constructor(private eRef: ElementRef) {
} }
@@ -31,8 +31,9 @@ export class ProfileComponent implements OnInit {
} }
if (!this.eRef.nativeElement.contains(event.target)) { if (!this.eRef.nativeElement.contains(event.target)) {
this.showProfilePopoverChange.emit(false); this.showProfilePopOverChange.emit(false);
} }
} }
// TODO: Close modal also when "escape" is clicked
} }
+112 -5
View File
@@ -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;
} }
+22 -7
View File
@@ -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
];
} }
+2 -2
View File
@@ -1,4 +1,4 @@
export enum Team { export enum Team {
RE = 0, RE = 'Re',
KONTRA = 1 CONTRA = 'Contra'
} }
-2
View File
@@ -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;
} }
+2
View File
@@ -1,10 +1,12 @@
import {Game} from './game'; import {Game} from './game';
import {Player} from './player'; import {Player} from './player';
import {GameRules} from './gamerules';
export interface GameNight { export interface GameNight {
gameNightId: number; gameNightId: number;
date: Date; date: Date;
players: Player[]; // We need players here and in the game because maybe we have 5 players for a game night and players: Player[]; // We need players here and in the game because maybe we have 5 players for a game night and
// they switch every game // they switch every game
rules: GameRules;
games: Game[]; games: Game[];
} }
+4
View File
@@ -0,0 +1,4 @@
export interface GameRules {
mandatoryAnnouncement: boolean;
}
+5 -3
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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;