Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tutorial #22

Merged
merged 2 commits into from
Mar 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
faFilePen,
faPuzzlePiece,
faChessBoard,
faMagnifyingGlass,
IconDefinition,
} from '@fortawesome/free-solid-svg-icons';

Expand All @@ -27,6 +28,7 @@ export class AppComponent {
githubUrl: string = 'https://github.com/UndefinedOnGitHub/chesspad-pp';

sideBarActions: SideBarAction[] = [
{ routerLink: '/tutorial', icon: faMagnifyingGlass, text: 'Tutorial' },
{ routerLink: '/', icon: faFilePen, text: 'Notepad' },
{ routerLink: '/puzzles', icon: faPuzzlePiece, text: 'Puzzle' },
{ routerLink: '/games', icon: faChessBoard, text: 'Game' },
Expand Down
3 changes: 3 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,12 @@ import { GameReviewComponent } from './game-review/game-review.component';
// Routing
import { RouterModule, Routes } from '@angular/router';
import { GameReviewSelectorDialogComponent } from './game-review-selector-dialog/game-review-selector-dialog.component';
import { TutorialComponent } from './tutorial/tutorial.component';
const routes: Routes = [
{ path: '', component: GameComponent },
{ path: 'puzzles', component: PuzzlesComponent },
{ path: 'games', component: GameReviewComponent },
{ path: 'tutorial', component: TutorialComponent },
];

@NgModule({
Expand All @@ -53,6 +55,7 @@ const routes: Routes = [
PuzzlesComponent,
GameReviewComponent,
GameReviewSelectorDialogComponent,
TutorialComponent,
],
imports: [
BrowserModule,
Expand Down
2 changes: 1 addition & 1 deletion src/app/finish-game-dialog/finish-game-dialog.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class FinishGameDialogComponent {
public notify: NotifyService,
) {
if (data.game) {
this.game.game = data.game
this.game.game = data.game;
}
this.gameString = data.pgn || this.game.exportPGN();
this.gameWinner = this.findGameWinner();
Expand Down
2 changes: 1 addition & 1 deletion src/app/game-review.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ export class GameReviewService {
lastMove: [gameMove.from, gameMove.to],
});
} else {
this.#finishGame()
this.#finishGame();
}
}, 500);
this.#scrollToLastMove();
Expand Down
8 changes: 7 additions & 1 deletion src/app/keyboard/keyboard.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { faGear } from '@fortawesome/free-solid-svg-icons';
import { GameService } from '../game.service';
import { PuzzleService } from '../puzzle.service';
import { GameReviewService } from '../game-review.service';
import { TutorialService } from '../tutorial.service';
import { MatDialog } from '@angular/material/dialog';
import { KeyboardSettingsDialogComponent } from '../keyboard-settings-dialog/keyboard-settings-dialog.component';

Expand All @@ -28,7 +29,12 @@ export class KeyboardComponent implements OnInit {
moveManager: Move = new Move();
// Keyboard Settings
keyboardSettings: KeyboardSettings = { allowSuggestions: false };
@Input() game: GameService | PuzzleService | GameReviewService | undefined;
@Input() game:
| GameService
| PuzzleService
| GameReviewService
| TutorialService
| undefined;

constructor(public dialog: MatDialog) {
this.game?.setMoveClickCallback((m: Move) => {
Expand Down
123 changes: 123 additions & 0 deletions src/app/tutorial-positions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { Move } from './move';

export interface Position {
title: string;
move: Move;
pgn: string;
hint: string;
}

const pawn_move_scenario: Position = {
title: 'Basic Pawn Move',
hint: 'This is a common first move. Enter e4 on the keyboard. Pressing the pawn icon is optional. Click on the "e" button then press the "#" to switch to the row options. Next click on "4". Finally click on the submit button "| |". <br/><code>e &#8594; # &#8594; 4 &#8594; ||</code>',
move: new Move('e4'),
pgn: `1. e4 *`,
};

const pawn_take: Position = {
title: 'Pawn Take',
hint: 'When a pawn takes you can make this move in 2 ways. First way is to select the source square by clicking on the "*" followed by the source column ("e"). Now that the source column is selected you can select the destination square ("d"). Next click on "x" to mark the move as a capture. With the columns selected you can click the "#" to see the rows and finally click "5" to select the destination row. The second way to enter a pawn take is to click on the source column then click on x and then click on the destination. This will move the first click to be the source and the second column to be the destination. <br/><code>e &#8594; x &#8594; d &#8594; # &#8594; 5 &#8594; ||</code><br/>or<br/><code>*e &#8594; d &#8594; x &#8594; # &#8594; 5 &#8594; ||</code>',
move: new Move('exd5'),
pgn: `
[FEN "rnbqkbnr/ppp1pppp/8/3p4/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 1"]

1. exd5 *
`,
};

const pawn_promotion: Position = {
title: 'Pawn Promotion To Queen',
hint: 'When a pawn advances to the last rank it promotes to another peice. To enter this press the column "d" the number pad "#" then the row "8". Next press the equals sign "=" then the promotion peice "Q".<br/><code>d &#8594; # &#8594; 8 &#8594; =Q &#8594; ||</code>',
move: new Move('d8=Q'),
pgn: `
[FEN "k7/3P4/7K/8/8/8/1Q6/8 w - - 0 1"]

1. d8=Q+ *
`,
};

const knight_takes: Position = {
title: 'Knight Take Move',
hint: '<br/><code>Knight &#8594; x &#8594; e &#8594; # &#8594; 5 &#8594; ||</code>',
move: new Move('Nxe5'),
pgn: `
[FEN "rnbqkbnr/pppp1ppp/8/4p3/8/5N2/PPPPPPPP/RNBQKB1R w KQkq - 0 1"]

1. Nxe5 *
`,
};

const rook_column_move: Position = {
title: 'Rooks On The Same Row',
hint: 'When you have two rooks on the same row you must declare which rook is to be moved. To select the source column first click the star icon * then click the source column d. Next click the destination column e. This order does not matter. By clicking the star the keyboard then expects a source square. After selecting the column click on the # to switch to the row options and click 4. <br/><code>Rook &#8594; *d &#8594; e &#8594; # &#8594; 4 &#8594; ||</code>',
move: new Move('Rde4'),
pgn: `
[FEN "1r2k3/8/8/8/3R1R2/8/2K5/7r w - - 0 1"]

1. Rde4+ *
`,
};

const rook_row_move: Position = {
title: 'Rooks On The Same Column',
hint: 'When you have two rooks on the same column you must declare which rook is to be moved. Start by pressing the destination column e. Then switch to the row options. Here click the source option selector then the source row. Next click the destination row. At any point in this process you may click the rook icon to define a rook move.<br/><code>Rook &#8594; e &#8594; # &#8594; *1 &#8594; 6 &#8594; ||</code>',
move: new Move('R1e6'),
pgn: `
[FEN "8/4R3/1k6/6K1/8/8/8/4R3 w - - 0 1"]

1. R1e6+ *
`,
};

const triple_queen_move: Position = {
title: 'Triple Queen Move',
hint: 'When you have 3 peices that can all go to the same square you must declare both the source column and the source row. For this you must first click on the * then click the source column. Then click the destination column. Move over to the row options. Here you can click the source selector star * then click on the source row. Finally click on the destination row. You can click on the queen peice anytime in this process.<br/><code>Queen &#8594; *g &#8594; e &#8594; # &#8594; *3 &#8594; 5 &#8594; ||</code>',
move: new Move('Qg3e5'),
pgn: `
[FEN "k7/6Q1/8/7K/8/2Q3Q1/8/8 w - - 0 1"]

1. Qg3e5 1/2-1/2
`,
};

const knight_row_move: Position = {
title: 'Knights On The Same Column',
hint: '<br/><code>Knight &#8594; d &#8594; # &#8594; *2 &#8594; 4 &#8594; ||</code>',
move: new Move('N2d4'),
pgn: `
[FEN "8/8/4N3/1k6/7K/8/4N3/8 w - - 0 1"]

1. N2d4+ *
`,
};

const knight_column_move: Position = {
title: 'Knights On The Same Row',
hint: '<br/><code>Knight &#8594; *f &#8594; d &#8594; # &#8594; 4 &#8594; ||</code>',
move: new Move('Nfd4'),
pgn: `
[FEN "8/8/8/1N3N1K/8/8/1k6/8 w - - 0 1"]

1. Nfd4 *
`,
};

const finished_position: Position = {
title: 'Finished',
hint: 'Tutorial finished. Thanks for using chesspad ++',
move: new Move(''),
pgn: ``,
};

export const positions: Position[] = [
pawn_move_scenario,
pawn_take,
pawn_promotion,
knight_takes,
rook_column_move,
rook_row_move,
knight_column_move,
knight_row_move,
triple_queen_move,
finished_position,
];
16 changes: 16 additions & 0 deletions src/app/tutorial.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { TutorialService } from './tutorial.service';

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

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

it('should be created', () => {
expect(service).toBeTruthy();
});
});
57 changes: 57 additions & 0 deletions src/app/tutorial.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Injectable } from '@angular/core';
import { Chess } from 'chess.js';
import { Move } from './move';
import { positions, Position } from './tutorial-positions';
import { Chessground } from 'chessground';

@Injectable({
providedIn: 'root',
})
export class TutorialService {
positionIdx: number = 0;
currentPosition: Position = positions[this.positionIdx];
game: Chess = new Chess();
element: HTMLElement | undefined | null;
groundboard: ReturnType<typeof Chessground> | undefined;

constructor() {}

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

#constructBoard() {
// Set Digital Board
if (this.element) {
this.game.loadPgn(this.currentPosition.pgn);
const m = this.game.history({ verbose: true })[0];
const config = {
coordinates: false,
fen: this.game.fen(),
viewOnly: true,
lastMove: m ? [m.from, m.to] : [],
};
this.groundboard = Chessground(this.element, config);
}
}

setMoveClickCallback() {}
getAdditionalButton() {
return null;
}
makeMove(move: Move): { sucess: boolean } {
if (this.currentPosition.move.toString() == move.toString()) {
this.positionIdx++;
const position = positions[this.positionIdx];
if (position) {
this.currentPosition = position;
this.init();
}
return { sucess: true };
}
return { sucess: false };
}
}
18 changes: 18 additions & 0 deletions src/app/tutorial/tutorial.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<div
class="d-flex flex-column flex-lg-row flex-xl-row flex-xs-column flex-sm-column flex-md-row p-2 tutorial-container"
>
<div class="flex-grow-1 p-1 mb-2 notepad-content-container">
<mat-card>
<mat-card-content class="d-flex justify-content-center">
<div id="tutorial-chessboard"></div>
</mat-card-content>
</mat-card>
<div class="d-flex flex-column display-results p-2">
<p>{{ tutorial.currentPosition.title }}</p>
<p [innerHTML]="tutorial.currentPosition.hint"></p>
</div>
</div>
<div class="d-flex p-1 justify-content-center">
<app-keyboard [game]="tutorial"> </app-keyboard>
</div>
</div>
40 changes: 40 additions & 0 deletions src/app/tutorial/tutorial.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
$boardSize: 300px - 100px;

#tutorial-chessboard {
width: $boardSize;
height: $boardSize;
}
@media (min-width: 576px) {
#tutorial-chessboard {
width: $boardSize;
height: $boardSize;
}
}
@media (min-width: 768px) {
#tutorial-chessboard {
width: $boardSize + 100px;
height: $boardSize + 100px;
}
}
@media (min-width: 992px) {
#tutorial-chessboard {
width: $boardSize + 200px;
height: $boardSize + 200px;
}
}
@media (min-width: 1200px) {
#tutorial-chessboard {
width: $boardSize + 300px;
height: $boardSize + 300px;
}
}

.tutorial-container {
height: 100%;
}

.display-results {
max-width: 400px;
max-height: 100px;
overflow: auto;
}
31 changes: 31 additions & 0 deletions src/app/tutorial/tutorial.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { TutorialComponent } from './tutorial.component';
import { MatCardModule } from '@angular/material/card';
import { KeyboardComponent } from '../keyboard/keyboard.component';
import { MatDialogModule } from '@angular/material/dialog';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { KeyboardButtonComponent } from '../keyboard-button/keyboard-button.component';

describe('TutorialComponent', () => {
let component: TutorialComponent;
let fixture: ComponentFixture<TutorialComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
TutorialComponent,
KeyboardComponent,
KeyboardButtonComponent,
],
imports: [MatCardModule, MatDialogModule, FontAwesomeModule],
});
fixture = TestBed.createComponent(TutorialComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
14 changes: 14 additions & 0 deletions src/app/tutorial/tutorial.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Component } from '@angular/core';
import { TutorialService } from '../tutorial.service';

@Component({
selector: 'app-tutorial',
templateUrl: './tutorial.component.html',
styleUrls: ['./tutorial.component.scss'],
})
export class TutorialComponent {
constructor(public tutorial: TutorialService) {}
ngOnInit() {
this.tutorial.init(document.getElementById('tutorial-chessboard'));
}
}
Loading