Added pre and post game UI states
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
.app-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
@@ -6,6 +6,11 @@
|
||||
|
||||
<div class="app-body">
|
||||
<!-- score-card component -->
|
||||
<!-- game-card (reactive form) component -->
|
||||
<ng-container *ngIf="(gameStatus$ | async) !== 'post'; else matchResults">
|
||||
<app-game-card></app-game-card>
|
||||
</ng-container>
|
||||
<ng-template #matchResults>
|
||||
<app-match-results></app-match-results>
|
||||
</ng-template>
|
||||
<!-- game-history component -->
|
||||
</div>
|
||||
@@ -1,9 +1,17 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { GameFacade } from './store/game';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent {
|
||||
gameStatus$ = this.gameFacade.gameStatus$;
|
||||
|
||||
constructor(
|
||||
private readonly gameFacade: GameFacade
|
||||
) { }
|
||||
|
||||
}
|
||||
|
||||
@@ -1,26 +1,47 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
|
||||
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatRadioModule } from '@angular/material/radio';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { GameStoreModule } from './store';
|
||||
import { DirectivesModule } from './directives/directives.module';
|
||||
|
||||
import { GameCardComponent } from './game-card/game-card.component';
|
||||
import { MatchResultsComponent } from './match-results/match-results.component';
|
||||
|
||||
const materialModules = [
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatIconModule,
|
||||
MatRadioModule,
|
||||
MatToolbarModule
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent
|
||||
AppComponent,
|
||||
GameCardComponent,
|
||||
MatchResultsComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
MatButtonModule,
|
||||
MatToolbarModule,
|
||||
ReactiveFormsModule,
|
||||
FormsModule,
|
||||
...materialModules,
|
||||
StoreModule.forRoot({}, {}),
|
||||
EffectsModule.forRoot([]),
|
||||
DirectivesModule,
|
||||
GameStoreModule
|
||||
],
|
||||
providers: [],
|
||||
|
||||
24
src/app/game-card/game-card.component.css
Normal file
24
src/app/game-card/game-card.component.css
Normal file
@@ -0,0 +1,24 @@
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.game-card {
|
||||
width: 420px;
|
||||
}
|
||||
|
||||
.full-width-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:host ::ng-deep .mat-radio-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
:host ::ng-deep .mat-radio-label {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
16
src/app/game-card/game-card.component.html
Normal file
16
src/app/game-card/game-card.component.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<mat-card class="game-card">
|
||||
<form [formGroup]="form" #gameForm="ngForm" (ngSubmit)="submit($event, gameForm)" novalidate>
|
||||
<mat-card-content>
|
||||
<mat-radio-group class="full-width-field" formControlName="playerChoice">
|
||||
<mat-radio-button class="rps-radio-button" *ngFor="let choice of choices" [value]="choice">
|
||||
<img src="./assets/{{ choice }}-50.png" [alt]="choice" />
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</mat-card-content>
|
||||
<mat-card-actions align="center">
|
||||
<button type="submit" mat-raised-button color="primary" [disabled]="!form.valid">
|
||||
<mat-icon>play_arrow</mat-icon>
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
</form>
|
||||
</mat-card>
|
||||
33
src/app/game-card/game-card.component.ts
Normal file
33
src/app/game-card/game-card.component.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
|
||||
import { GameFacade } from "../store/game";
|
||||
|
||||
@Component({
|
||||
selector: 'app-game-card',
|
||||
templateUrl: './game-card.component.html',
|
||||
styleUrls: ['./game-card.component.css']
|
||||
})
|
||||
export class GameCardComponent implements OnInit {
|
||||
form: FormGroup;
|
||||
choices = ['rock', 'paper', 'scissors'];
|
||||
|
||||
constructor(
|
||||
private readonly fb: FormBuilder,
|
||||
private readonly gameFacade: GameFacade
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.form = this.fb.group({
|
||||
playerChoice: [undefined, [Validators.required]]
|
||||
});
|
||||
}
|
||||
|
||||
submit(event, gameForm): void {
|
||||
event.preventDefault();
|
||||
this.gameFacade.play(this.form.value.playerChoice);
|
||||
this.form.reset();
|
||||
gameForm.resetForm();
|
||||
}
|
||||
|
||||
}
|
||||
23
src/app/match-results/match-results.component.css
Normal file
23
src/app/match-results/match-results.component.css
Normal file
@@ -0,0 +1,23 @@
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.match-results {
|
||||
width: 420px;
|
||||
}
|
||||
|
||||
.match-results-text {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.match-visual {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 1rem 0;
|
||||
justify-content: space-around;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.match-visual .vs {
|
||||
font-size: 3rem;
|
||||
}
|
||||
17
src/app/match-results/match-results.component.html
Normal file
17
src/app/match-results/match-results.component.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<mat-card *ngLet="results$ | async; let results;" class="match-results">
|
||||
<mat-card-title>Match Results</mat-card-title>
|
||||
<!-- {"playerChoice":"rock","computerChoice":"scissors","result":"win"} -->
|
||||
<mat-card-content>
|
||||
<div class="match-results-text">You {{ results.result }}!</div>
|
||||
<div class="match-visual">
|
||||
<img src="./assets/{{ results.playerChoice }}-50.png" [alt]="results.playerChoice">
|
||||
<span class="vs">vs</span>
|
||||
<img src="./assets/{{ results.computerChoice }}-50.png" [alt]="results.computerChoice">
|
||||
</div>
|
||||
</mat-card-content>
|
||||
<mat-card-actions align="center">
|
||||
<button type="button" mat-raised-button color="primary" (click)="playAgain()">
|
||||
Play Again
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
21
src/app/match-results/match-results.component.ts
Normal file
21
src/app/match-results/match-results.component.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { GameFacade } from '../store/game';
|
||||
|
||||
@Component({
|
||||
selector: 'app-match-results',
|
||||
templateUrl: './match-results.component.html',
|
||||
styleUrls: ['./match-results.component.css']
|
||||
})
|
||||
export class MatchResultsComponent {
|
||||
results$ = this.gameFacade.recentGameResults$;
|
||||
|
||||
constructor(
|
||||
private readonly gameFacade: GameFacade
|
||||
) { }
|
||||
|
||||
playAgain(): void {
|
||||
this.gameFacade.reset();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +1,25 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Store, select } from '@ngrx/store';
|
||||
|
||||
import * as gameActions from './game.actions';
|
||||
import * as GameActions from './game.actions';
|
||||
import * as GameQuery from './game.selectors';
|
||||
|
||||
@Injectable()
|
||||
export class GameFacade {
|
||||
recentGameResults$ = this.store.pipe(select(GameQuery.selectRecentMatchResults));
|
||||
// gameHistory$ = this.store.pipe(select(getGameHistory));
|
||||
// scores$ = this.store.pipe(select(getScores));
|
||||
// gameStatus$ = this.store.pipe(select(getGameStatus));
|
||||
gameStatus$ = this.store.pipe(select(GameQuery.selectGameStatus));
|
||||
|
||||
constructor(
|
||||
private readonly store: Store
|
||||
) { }
|
||||
|
||||
play(playerChoice: string): void {
|
||||
this.store.dispatch(gameActions.playMatch({ playerChoice }));
|
||||
this.store.dispatch(GameActions.playMatch({ playerChoice }));
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.store.dispatch(gameActions.resetMatch());
|
||||
this.store.dispatch(GameActions.resetMatch());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,3 +4,5 @@ import * as fromGame from './game.reducer';
|
||||
export const selectGameState = createFeatureSelector<fromGame.State>(
|
||||
fromGame.gameFeatureKey
|
||||
);
|
||||
export const selectGameStatus = createSelector(selectGameState, fromGame.getGameStatus);
|
||||
export const selectRecentMatchResults = createSelector(selectGameState, fromGame.getRecentMatchHistory);
|
||||
Reference in New Issue
Block a user