Skip to content

Commit

Permalink
Merge pull request #19 from UndefinedOnGitHub/add-game-storage
Browse files Browse the repository at this point in the history
Add game storage
  • Loading branch information
UndefinedOnGitHub authored Feb 17, 2024
2 parents 6f70612 + 8cbad95 commit 15baeff
Show file tree
Hide file tree
Showing 14 changed files with 236 additions and 96 deletions.
25 changes: 5 additions & 20 deletions src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div class="app-container">
<mat-toolbar color="primary">
<button mat-icon-button class="" aria-label="" (click)="drawer.toggle()">
<button mat-icon-button class="" (click)="drawer.toggle()">
<fa-icon [icon]="faBars"></fa-icon>
</button>
<span routerLink="/" id="chesspad-pp-title" class="chesspad-pp-title">
Expand Down Expand Up @@ -32,28 +32,13 @@
<mat-drawer #drawer mode="over" class="mat-drawer">
<div class="d-flex flex-column p-3 navigation-menu">
<div
*ngFor="let action of sideBarActions"
class="d-flex align-content-center"
routerLink="/"
routerLink="{{action.routerLink}}"
(click)="drawer.close()"
>
<fa-icon [icon]="faFilePen"></fa-icon>
<span>Notepad</span>
</div>
<div
class="d-flex align-content-center"
routerLink="/puzzles"
(click)="drawer.close()"
>
<fa-icon [icon]="faPuzzlePiece"></fa-icon>
<span>Puzzle</span>
</div>
<div
class="d-flex align-content-center"
routerLink="/games"
(click)="drawer.close()"
>
<fa-icon [icon]="faChessBoard"></fa-icon>
<span>Game</span>
<fa-icon [icon]="action.icon"></fa-icon>
<span>{{action.text}}</span>
</div>
</div>
</mat-drawer>
Expand Down
20 changes: 12 additions & 8 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,35 @@ import { Component } from '@angular/core';
import { Move } from './move';
import {
faBars,
faHeart,
faShare,
faCircleStop,
faFilePen,
faPuzzlePiece,
faChessBoard,
IconDefinition,
} from '@fortawesome/free-solid-svg-icons';

interface SideBarAction {
routerLink: string;
icon: IconDefinition;
text: string;
}

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
title = 'chesspad.pp';
faBars: IconDefinition = faBars;
faHeart: IconDefinition = faHeart;
faShare: IconDefinition = faShare;
faFilePen: IconDefinition = faFilePen;
faPuzzlePiece: IconDefinition = faPuzzlePiece;
faChessBoard: IconDefinition = faChessBoard;
faCircleStop: IconDefinition = faCircleStop;

githubUrl: string = 'https://github.com/UndefinedOnGitHub/chesspad-pp';

sideBarActions : SideBarAction[] = [
{routerLink: "/", icon: faFilePen, text: "Notepad"},
{routerLink: "/puzzles", icon: faPuzzlePiece, text: "Puzzle"},
{routerLink: "/games", icon: faChessBoard, text: "Game"},
]

constructor() {}
}
7 changes: 5 additions & 2 deletions src/app/finish-game-dialog/finish-game-dialog.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import {
} from '@angular/material/dialog';
import { GameService } from '../game.service';
import { NotifyService } from '../notify.service';
import { Chess } from 'chess.js';
export interface DialogData {
pgn: string;
disabled?: boolean;
game?: Chess;
}

@Component({
Expand All @@ -32,12 +34,13 @@ export class FinishGameDialogComponent {
) {
this.gameString = data.pgn || this.game.exportPGN();
this.gameWinner = this.findGameWinner();
this.onGameWinnerChange({value: this.gameWinner});
this.gameDisabled = data.disabled || false;
}

findGameWinner(): '1-0' | '1/2-1/2' | '0-1' {
if (this.game.isCheckmate()) {
return this.game.currentTurn() == 'w' ? '1-0' : '0-1';
if (this.data?.game && this.data?.game?.isCheckmate()) {
return this.data.game.turn() != 'w' ? '1-0' : '0-1';
}
return '1/2-1/2';
}
Expand Down
132 changes: 88 additions & 44 deletions src/app/game-review.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
DialogCloseResponse,
} from './game-review-selector-dialog/game-review-selector-dialog.component';
import { KeyboardButton } from './button';
import { GameStorageManagerService } from './game-storage-manager.service';
import { faNotesMedical } from '@fortawesome/free-solid-svg-icons';

@Injectable({
Expand All @@ -28,70 +29,95 @@ export class GameReviewService {
icon: faNotesMedical,
symbol: '',
onTrigger: () => {
this.loadGame();
this.#newGamePopup();
},
});

constructor(
public api: ChessWebsiteApiService,
public dialog: MatDialog,
public storage: GameStorageManagerService,
) {}

getAdditionalButton() {
return this.additionalButton;
}

init(element: HTMLElement | null = null) : void {
return this.#loadGame(element);
}

// Not in use. May be needed if board loading takes a long time.
startBoardLoading(
board: ReturnType<typeof Chessground>,
): ReturnType<typeof setTimeout> {
let peices = [
['c4', null, 'white'],
['d4', null, 'white'],
['e4', null, 'white'],
['f4', null, 'white'],
];
return setInterval(() => {
const [position, peice, color] = peices.shift() || [];
if (peice) {
this.groundboard.setPieces([[position, { role: peice, color: color }]]);
peices.push([position, null, color == 'black' ? 'white' : 'black']);
} else {
this.groundboard.setPieces([[position, null]]);
peices.push([position, 'king', color]);
// startBoardLoading(
// board: ReturnType<typeof Chessground>,
// ): ReturnType<typeof setTimeout> {
// let peices = [
// ['c4', null, 'white'],
// ['d4', null, 'white'],
// ['e4', null, 'white'],
// ['f4', null, 'white'],
// ];
// return setInterval(() => {
// const [position, peice, color] = peices.shift() || [];
// if (peice) {
// this.groundboard.setPieces([[position, { role: peice, color: color }]]);
// peices.push([position, null, color == 'black' ? 'white' : 'black']);
// } else {
// this.groundboard.setPieces([[position, null]]);
// peices.push([position, 'king', color]);
// }
// }, 200);
// }

#newGamePopup() {
const dialogRef = this.dialog.open(GameReviewSelectorDialogComponent, {
data: {},
});
dialogRef.afterClosed().subscribe((result: DialogCloseResponse) => {
if (result) {
this.#fetchAndLoadGame(result);
}
}, 200);
});
}

loadGame(element: HTMLElement | null = null): void {
#loadGame(element: HTMLElement | null = null): void {
if (element) {
this.element = element;
this.groundboard = Chessground(this.element, {
coordinates: false,
viewOnly: true,
});
this.game = new Chess();
}
const dialogRef = this.dialog.open(GameReviewSelectorDialogComponent, {
data: {},
});
dialogRef.afterClosed().subscribe((result: DialogCloseResponse) => {
if (result) {
if (this.element) {
this.groundboard = Chessground(this.element, {
coordinates: false,
viewOnly: true,
});
}
this.game = new Chess();
const promise = this.api.fetchChessGame(result.username, result.color);
promise.subscribe((response: GameResponse) => {
this.setGameFromResponse(response);
});
}
const game = this.storage.fetchGame("local_game_review")
if (game) {
const moveNumber = parseInt(this.storage.fetch("local_game_review_move_number") || "0") || 0;
const history = game.history().slice(moveNumber);
game.history().slice(0, moveNumber).forEach(m => this.game.move(m));
const orientation = this.storage.fetch("local_game_review_orientation") == "b" ? "b" : "w";

this.#initiateGame({history, orientation, gamePgn: game.pgn()})
this.#scrollToLastMove();
} else {
this.#newGamePopup();
}
}

#fetchAndLoadGame(result: DialogCloseResponse) : void {
if (this.element) {
this.groundboard = Chessground(this.element, {
coordinates: false,
viewOnly: true,
});
}
this.game = new Chess();
const promise = this.api.fetchChessGame(result.username, result.color);
promise.subscribe((response: GameResponse) => {
this.#setGameFromResponse(response);
});
}

constructConfig(response: GameResponse, firstMove: any) {
#constructConfig(response: GameResponse, firstMove: any) {
const orientation: 'white' | 'black' =
response.orientation == 'w' ? 'white' : 'black';

Expand All @@ -104,24 +130,37 @@ export class GameReviewService {
};
}

initiateGame(response: GameResponse): void {
#storeGame(response: GameResponse) {
const storageGame = new Chess();
storageGame.loadPgn(response.gamePgn);
this.storage.storeGame("local_game_review", storageGame);
this.storage.store("local_game_review_move_number", "0");
this.storage.store("local_game_review_orientation", response.orientation);
}

#initiateGame(response: GameResponse): void {
// Set Game
console.log(this.game.ascii());
this.history = response.history;
console.log(this.history);
this.currentMove = this.history.shift();
if (this.currentMove) {
const firstMove = this.game.move(this.currentMove);
const config = this.constructConfig(response, firstMove);
const config = this.#constructConfig(response, firstMove);
// Set Digital Board
if (this.element) {
this.groundboard = Chessground(this.element, config);
}
}

// Log Game
console.log(this.game.ascii());
console.log(this.history);
}

setGameFromResponse(response: GameResponse): void {
this.initiateGame(response);
#setGameFromResponse(response: GameResponse): void {
// Store Game
this.#storeGame(response);
// Start Game
this.#initiateGame(response);
}

setMoveClickCallback() {}
Expand All @@ -132,6 +171,7 @@ export class GameReviewService {
setTimeout(() => {
if (this.currentMove) {
const gameMove = this.game.move(this.currentMove);
this.storage.store("local_game_review_move_number", String(this.game.history().length - 1))
this.groundboard.set({
fen: this.game.fen(),
lastMove: [gameMove.from, gameMove.to],
Expand All @@ -144,6 +184,10 @@ export class GameReviewService {
return { sucess: false };
}

isCheckmate() : boolean {
return this.game.isCheckmate();
}

#scrollToLastMove(): void {
// Scroll to last move
// Keep slight delay to force the render of the move before animation
Expand Down
2 changes: 1 addition & 1 deletion src/app/game-review/game-review.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ export class GameReviewComponent implements OnInit {
}

ngOnInit() {
this.gameReview.loadGame(document.getElementById('chessboard'));
this.gameReview.init(document.getElementById('chessboard'));
}
}
16 changes: 16 additions & 0 deletions src/app/game-storage-manager.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { GameStorageManagerService } from './game-storage-manager.service';

describe('GameStorageManagerService', () => {
let service: GameStorageManagerService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(GameStorageManagerService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
50 changes: 50 additions & 0 deletions src/app/game-storage-manager.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Injectable } from '@angular/core';
import { Chess } from 'chess.js';

@Injectable({
providedIn: 'root'
})
export class GameStorageManagerService {

constructor() { }

storeGame(key: string, game: Chess): void {
try {
const pgn64 = btoa(game.pgn());
this.store(key, pgn64);
} catch {
console.error("Failed to store game")
}
}

fetchGame(key: string) : Chess | undefined {
try {
const pgn64 = this.fetch(key) || '';
if (pgn64) {
const pgn = atob(pgn64);
const game = new Chess();
game.loadPgn(pgn);
return game
}
} catch (err) {
console.error("Failed to fetch game", err);
}
return;
}

clearGame(key: string): void {
localStorage.removeItem(key)
}

isGameStored(key: string): boolean {
return (localStorage.getItem(key) || '').length > 0;
}

store(key : string, value : string) {
localStorage.setItem(key, value);
}

fetch(key : string) {
return localStorage.getItem(key);
}
}
Loading

0 comments on commit 15baeff

Please sign in to comment.