├── src ├── assets │ └── .gitkeep ├── app │ ├── components │ │ ├── fen │ │ │ ├── fen.component.scss │ │ │ ├── fen.component.html │ │ │ ├── fen.component.ts │ │ │ └── fen.component.spec.ts │ │ ├── actions │ │ │ ├── actions.component.scss │ │ │ ├── actions.component.html │ │ │ ├── actions.component.ts │ │ │ └── actions.component.spec.ts │ │ ├── settings │ │ │ ├── settings.component.ts │ │ │ ├── settings.component.spec.ts │ │ │ ├── settings.component.scss │ │ │ └── settings.component.html │ │ └── moves │ │ │ ├── moves.component.scss │ │ │ ├── moves.component.spec.ts │ │ │ ├── moves.component.html │ │ │ └── moves.component.ts │ ├── app.module.ts │ ├── app.component.spec.ts │ ├── app.component.scss │ ├── app.component.ts │ └── app.component.html ├── favicon.ico ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── styles.scss ├── main.ts ├── test.ts ├── index.html └── polyfills.ts ├── projects └── ngx-chess-board │ ├── .gitignore │ ├── src │ ├── lib │ │ ├── models │ │ │ ├── pieces │ │ │ │ ├── color.ts │ │ │ │ ├── point.ts │ │ │ │ ├── piece.ts │ │ │ │ ├── pawn.ts │ │ │ │ ├── knight.ts │ │ │ │ ├── rook.ts │ │ │ │ └── bishop.ts │ │ │ ├── move-translation.ts │ │ │ └── board.ts │ │ ├── size │ │ │ └── size-manager.ts │ │ ├── engine │ │ │ ├── outputs │ │ │ │ └── move-change │ │ │ │ │ ├── pgn-output.ts │ │ │ │ │ └── move-change.ts │ │ │ ├── drag │ │ │ │ ├── start │ │ │ │ │ ├── drag-start-processor.ts │ │ │ │ │ ├── animation-drag-start-processor.ts │ │ │ │ │ ├── default-drag-start-processor.ts │ │ │ │ │ └── drag-start-strategy.ts │ │ │ │ └── end │ │ │ │ │ ├── drag-end-processor.ts │ │ │ │ │ ├── animation-drag-end-processor.ts │ │ │ │ │ ├── default-drag-end-processor.ts │ │ │ │ │ └── drag-end-strategy.ts │ │ │ ├── drawing-tools │ │ │ │ ├── colors │ │ │ │ │ ├── color-processor.ts │ │ │ │ │ ├── default-color-processor.ts │ │ │ │ │ └── color-strategy.ts │ │ │ │ ├── shapes │ │ │ │ │ ├── circle.ts │ │ │ │ │ └── arrow.ts │ │ │ │ ├── draw-point.ts │ │ │ │ └── draw-provider.ts │ │ │ ├── piece-decorator │ │ │ │ ├── abstract-piece.ts │ │ │ │ ├── piece-abstract-decorator.ts │ │ │ │ └── available-move-decorator.ts │ │ │ ├── board-state-provider │ │ │ │ ├── board-state │ │ │ │ │ ├── board-state.ts │ │ │ │ │ ├── board-state-provider.ts │ │ │ │ │ └── move-state-provider.ts │ │ │ │ └── board-loader │ │ │ │ │ ├── notation-processors │ │ │ │ │ ├── notation-processor.ts │ │ │ │ │ ├── notation-processor-factory.ts │ │ │ │ │ └── fen-loader │ │ │ │ │ │ └── default-fen-processor.ts │ │ │ │ │ ├── board-loader.ts │ │ │ │ │ └── default-pieces-loader.ts │ │ │ ├── coords │ │ │ │ └── coords-provider.ts │ │ │ ├── click │ │ │ │ └── click-utils.ts │ │ │ ├── pgn │ │ │ │ ├── abstract-pgn-processor.ts │ │ │ │ └── default-pgn-processor.ts │ │ │ ├── utils │ │ │ │ └── piece-factory.ts │ │ │ └── abstract-engine-facade.ts │ │ ├── piece-promotion │ │ │ ├── piece-promotion-facade.ts │ │ │ ├── piece-promotion-modal │ │ │ │ ├── piece-promotion-modal.component.spec.ts │ │ │ │ ├── piece-promotion-modal.component.html │ │ │ │ ├── piece-promotion-modal.component.scss │ │ │ │ └── piece-promotion-modal.component.ts │ │ │ └── piece-promotion-resolver.ts │ │ ├── utils │ │ │ ├── inputs │ │ │ │ ├── piece-type-input.ts │ │ │ │ ├── piece-icon-input.ts │ │ │ │ └── piece-icon-input-manager.ts │ │ │ ├── constants.ts │ │ │ ├── unicode-constants.ts │ │ │ └── move-utils.ts │ │ ├── ngx-chess-board-view.ts │ │ ├── ngx-chess-board.module.ts │ │ ├── ngx-chess-board.component.spec.ts │ │ ├── history-move-provider │ │ │ ├── history-move.ts │ │ │ └── history-move-provider.ts │ │ ├── ngx-chess-board.component.scss │ │ ├── ngx-chess-board.component.html │ │ └── ngx-chess-board.component.ts │ ├── test.ts │ └── public-api.ts │ ├── ng-package.json │ ├── tsconfig.lib.prod.json │ ├── tsconfig.spec.json │ ├── tslint.json │ ├── tsconfig.lib.json │ ├── package.json │ ├── karma.conf.js │ └── README.md ├── e2e ├── tsconfig.json ├── src │ ├── app.po.ts │ └── app.e2e-spec.ts └── protractor.conf.js ├── tsconfig.spec.json ├── tsconfig.app.json ├── .browserslistrc ├── .circleci └── config.yml ├── .gitignore ├── tsconfig.json ├── LICENSE ├── karma.conf.js ├── package.json ├── tslint.json ├── angular.json └── README.md /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | -------------------------------------------------------------------------------- /src/app/components/fen/fen.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | width: 100%; 3 | } -------------------------------------------------------------------------------- /src/app/components/actions/actions.component.scss: -------------------------------------------------------------------------------- 1 | button { 2 | width: 100%; 3 | } -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grzegorz103/ngx-chess-board/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/models/pieces/color.ts: -------------------------------------------------------------------------------- 1 | export enum Color { 2 | WHITE, 3 | BLACK, 4 | } 5 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/size/size-manager.ts: -------------------------------------------------------------------------------- 1 | export class SizeManager { 2 | 3 | size: number; 4 | 5 | 6 | } 7 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/outputs/move-change/pgn-output.ts: -------------------------------------------------------------------------------- 1 | export class PgnOutput{ 2 | 3 | pgn: string; 4 | 5 | } 6 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/ngx-chess-board", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | body { 3 | background-color: #333333; 4 | color: white; 5 | } 6 | 7 | html, 8 | body { 9 | height: 100%; 10 | } 11 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.lib.json", 3 | "compilerOptions": { 4 | "declarationMap": false 5 | }, 6 | "angularCompilerOptions": { 7 | "compilationMode": "partial" 8 | } 9 | } -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/drag/start/drag-start-processor.ts: -------------------------------------------------------------------------------- 1 | import { CdkDragStart } from '@angular/cdk/drag-drop'; 2 | 3 | export interface DragStartProcessor { 4 | 5 | dragStarted: (event: CdkDragStart) => void; 6 | 7 | } 8 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/drawing-tools/colors/color-processor.ts: -------------------------------------------------------------------------------- 1 | import { CdkDragEnd } from '@angular/cdk/drag-drop'; 2 | 3 | export interface ColorProcessor { 4 | 5 | resolve: (ctrl: any, shift: any, alt: any) => string; 6 | 7 | } 8 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/drag/end/drag-end-processor.ts: -------------------------------------------------------------------------------- 1 | import { CdkDragEnd } from '@angular/cdk/drag-drop'; 2 | 3 | export interface DragEndProcessor { 4 | 5 | dragEnded: (event: CdkDragEnd, disabling: boolean, startTrans: string) => void; 6 | 7 | } 8 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/piece-decorator/abstract-piece.ts: -------------------------------------------------------------------------------- 1 | import { Point } from '../../models/pieces/point'; 2 | 3 | export interface AbstractPiece { 4 | 5 | getPossibleMoves(): Point[]; 6 | 7 | getPossibleCaptures(): Point[]; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/piece-promotion/piece-promotion-facade.ts: -------------------------------------------------------------------------------- 1 | import { PiecePromotionModalComponent } from 'ngx-chess-board'; 2 | 3 | export class PiecePromotionFacade { 4 | 5 | open(modal: PiecePromotionModalComponent): void { 6 | 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es2018", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/board-state-provider/board-state/board-state.ts: -------------------------------------------------------------------------------- 1 | import { Board } from '../../../models/board'; 2 | 3 | export class BoardState { 4 | 5 | board: Board; 6 | 7 | constructor(board: Board) { 8 | this.board = board; 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/drawing-tools/shapes/circle.ts: -------------------------------------------------------------------------------- 1 | import { DrawPoint } from '../draw-point'; 2 | 3 | export class Circle { 4 | drawPoint: DrawPoint; 5 | 6 | isEqual(circle: Circle) { 7 | return circle && this.drawPoint.isEqual(circle.drawPoint); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/utils/inputs/piece-type-input.ts: -------------------------------------------------------------------------------- 1 | export enum PieceTypeInput { 2 | 3 | KING = 1, 4 | QUEEN = 2, 5 | BISHOP = 3, 6 | KNIGHT = 4, 7 | ROOK = 5, 8 | PAWN = 6 9 | 10 | } 11 | 12 | export enum ColorInput { 13 | 14 | LIGHT = 1, 15 | DARK = 2 16 | 17 | } 18 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('app-root .content span')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/board-state-provider/board-loader/notation-processors/notation-processor.ts: -------------------------------------------------------------------------------- 1 | import { AbstractEngineFacade } from '../../../abstract-engine-facade'; 2 | 3 | export interface NotationProcessor { 4 | 5 | process: (notation: string, engineFacade: AbstractEngineFacade) => void; 6 | 7 | } 8 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/drawing-tools/shapes/arrow.ts: -------------------------------------------------------------------------------- 1 | import { DrawPoint } from '../draw-point'; 2 | 3 | export class Arrow { 4 | start: DrawPoint; 5 | end: DrawPoint; 6 | 7 | isEqual(arrow: Arrow) { 8 | return arrow && this.start.isEqual(arrow.start) && this.end.isEqual(arrow.end); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts" 12 | ], 13 | "include": [ 14 | "**/*.spec.ts", 15 | "**/*.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": ["node"] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ], 14 | "exclude": [ 15 | "src/test.ts", 16 | "src/**/*.spec.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "camelCase" 8 | ], 9 | "component-selector": [ 10 | true, 11 | "element", 12 | "kebab-case" 13 | ] 14 | }, 15 | "compilerOptions": { 16 | "skipLibCheck": true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/app/components/settings/settings.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, EventEmitter, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-settings', 5 | templateUrl: './settings.component.html', 6 | styleUrls: ['./settings.component.scss'], 7 | changeDetection: ChangeDetectionStrategy.OnPush, 8 | }) 9 | export class SettingsComponent { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/app/components/fen/fen.component.html: -------------------------------------------------------------------------------- 1 |
2 | 10 |
11 | 17 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/outputs/move-change/move-change.ts: -------------------------------------------------------------------------------- 1 | import { PgnOutput } from './pgn-output'; 2 | 3 | export interface MoveChange { 4 | move: string; 5 | piece: string; 6 | color: string; 7 | x: boolean; 8 | mate: boolean; 9 | 10 | check: boolean; 11 | stalemate: boolean; 12 | checkmate: boolean; 13 | fen: string; 14 | pgn: PgnOutput; 15 | freeMode: boolean; 16 | } 17 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/drawing-tools/draw-point.ts: -------------------------------------------------------------------------------- 1 | export class DrawPoint { 2 | x: number; 3 | y: number; 4 | color: string; 5 | 6 | constructor(x: number, y: number, color: string) { 7 | this.x = x + 0.5; 8 | this.y = y + 0.5; 9 | this.color = color; 10 | } 11 | 12 | isEqual(that: DrawPoint) { 13 | return that && that.x === this.x && this.y === that.y; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | import 'zone.js'; 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | platformBrowserDynamic().bootstrapModule(AppModule) 13 | .catch(err => console.error(err)); 14 | -------------------------------------------------------------------------------- /src/app/components/moves/moves.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: flex; 3 | flex-wrap: nowrap; 4 | } 5 | 6 | .history-moves { 7 | display: flex; 8 | flex-direction: column; 9 | width: 45%; 10 | min-width: 50px; 11 | .index-and-move { 12 | display: flex; 13 | } 14 | } 15 | 16 | .history-move { 17 | cursor: pointer; 18 | &:hover { 19 | background-color: rgba(0, 0, 0, 0.3); 20 | } 21 | width: 100%; 22 | } 23 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/drag/start/animation-drag-start-processor.ts: -------------------------------------------------------------------------------- 1 | import { CdkDragStart } from '@angular/cdk/drag-drop'; 2 | import { DragStartProcessor } from './drag-start-processor'; 3 | 4 | export class AnimationDragStartProcessor implements DragStartProcessor { 5 | 6 | dragStarted(event: CdkDragStart) { 7 | const style = event.source.getRootElement().style; 8 | style.zIndex = '1000'; 9 | style.position = 'absolute'; 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/utils/inputs/piece-icon-input.ts: -------------------------------------------------------------------------------- 1 | export interface PieceIconInput{ 2 | 3 | whiteKingUrl: string; 4 | whiteQueenUrl: string; 5 | whiteKnightUrl: string; 6 | whiteRookUrl: string; 7 | whitePawnUrl: string; 8 | whiteBishopUrl: string; 9 | 10 | blackKingUrl: string; 11 | blackQueenUrl: string; 12 | blackKnightUrl: string; 13 | blackRookUrl: string; 14 | blackPawnUrl: string; 15 | blackBishopUrl: string; 16 | 17 | } -------------------------------------------------------------------------------- /src/app/components/actions/actions.component.html: -------------------------------------------------------------------------------- 1 | 7 | 13 | 19 | 25 | -------------------------------------------------------------------------------- /src/app/components/fen/fen.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, EventEmitter, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-fen', 5 | templateUrl: './fen.component.html', 6 | styleUrls: ['./fen.component.scss'], 7 | changeDetection: ChangeDetectionStrategy.OnPush, 8 | }) 9 | export class FenComponent { 10 | @Input() fen = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'; 11 | @Input() fenChange = new EventEmitter(); 12 | } 13 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/piece-decorator/piece-abstract-decorator.ts: -------------------------------------------------------------------------------- 1 | import { Point } from '../../models/pieces/point'; 2 | import { AbstractPiece } from './abstract-piece'; 3 | 4 | export abstract class PieceAbstractDecorator implements AbstractPiece { 5 | 6 | piece: AbstractPiece; 7 | 8 | protected constructor(piece: AbstractPiece) { 9 | this.piece = piece; 10 | } 11 | 12 | abstract getPossibleCaptures(): Point[]; 13 | 14 | abstract getPossibleMoves(): Point[]; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/drag/end/animation-drag-end-processor.ts: -------------------------------------------------------------------------------- 1 | import { CdkDragEnd, CdkDragStart } from '@angular/cdk/drag-drop'; 2 | import { DragEndProcessor } from './drag-end-processor'; 3 | 4 | export class AnimationDragEndProcessor implements DragEndProcessor { 5 | 6 | dragEnded(event: CdkDragEnd, disabling: boolean, startTrans: string) { 7 | if (!disabling) { 8 | if (startTrans) { 9 | event.source._dragRef.getRootElement().style.transform = startTrans; 10 | } 11 | } 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/drag/start/default-drag-start-processor.ts: -------------------------------------------------------------------------------- 1 | import { CdkDragStart } from '@angular/cdk/drag-drop'; 2 | import { DragStartProcessor } from './drag-start-processor'; 3 | 4 | export class DefaultDragStartProcessor implements DragStartProcessor { 5 | 6 | dragStarted(event: CdkDragStart) { 7 | const style = event.source.element.nativeElement.style; 8 | style.position = 'relative'; 9 | style.zIndex = '1000'; 10 | style.touchAction = 'none'; 11 | style.pointerEvents = 'none'; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import { getTestBed } from '@angular/core/testing'; 4 | import { 5 | BrowserDynamicTestingModule, 6 | platformBrowserDynamicTesting 7 | } from '@angular/platform-browser-dynamic/testing'; 8 | import 'zone.js/testing'; 9 | 10 | // First, initialize the Angular testing environment. 11 | getTestBed().initTestEnvironment( 12 | BrowserDynamicTestingModule, 13 | platformBrowserDynamicTesting(), { 14 | teardown: { destroyAfterEach: false } 15 | } 16 | ); 17 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/drawing-tools/colors/default-color-processor.ts: -------------------------------------------------------------------------------- 1 | import { ColorProcessor } from './color-processor'; 2 | 3 | export class DefaultColorProcessor implements ColorProcessor{ 4 | 5 | resolve(ctrl: any, shift: any, alt: any): string{ 6 | let color = 'green'; 7 | 8 | if (ctrl || shift) { 9 | color = 'red'; 10 | } 11 | if (alt) { 12 | color = 'blue'; 13 | } 14 | if ((shift || ctrl) && alt) { 15 | color = 'orange'; 16 | } 17 | 18 | return color; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/utils/constants.ts: -------------------------------------------------------------------------------- 1 | export class Constants { 2 | static readonly DEFAULT_DARK_TILE_COLOR = 'rgb(97, 84, 61)'; 3 | static readonly DEFAULT_LIGHT_TILE_COLOR = '#BAA378'; 4 | 5 | static readonly DEFAULT_SIZE = 500; 6 | static readonly MIN_BOARD_SIZE = 100; 7 | static readonly MAX_BOARD_SIZE = 4000; 8 | 9 | static readonly DEFAULT_SOURCE_POINT_COLOR = 'rgba(146, 111, 26, 0.79)'; 10 | static readonly DEFAULT_DESTINATION_POINT_COLOR = '#b28e1a'; 11 | static readonly DEFAULT_LEGAL_MOVE_POINT_COLOR = 'radial-gradient(#13262F 15%, transparent 20%);'; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Chess 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import { getTestBed } from '@angular/core/testing'; 4 | import { 5 | BrowserDynamicTestingModule, 6 | platformBrowserDynamicTesting 7 | } from '@angular/platform-browser-dynamic/testing'; 8 | import 'zone.js'; 9 | import 'zone.js/testing'; 10 | 11 | // First, initialize the Angular testing environment. 12 | getTestBed().initTestEnvironment( 13 | BrowserDynamicTestingModule, 14 | platformBrowserDynamicTesting(), { 15 | teardown: { destroyAfterEach: false } 16 | } 17 | ); 18 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/drag/end/default-drag-end-processor.ts: -------------------------------------------------------------------------------- 1 | import { CdkDragEnd, CdkDragStart } from '@angular/cdk/drag-drop'; 2 | import { DragEndProcessor } from './drag-end-processor'; 3 | 4 | export class DefaultDragEndProcessor implements DragEndProcessor { 5 | 6 | dragEnded(event: CdkDragEnd, disabling: boolean, startTrans: string) { 7 | event.source.reset(); 8 | event.source.element.nativeElement.style.zIndex = '0'; 9 | event.source.element.nativeElement.style.pointerEvents = 'auto'; 10 | event.source.element.nativeElement.style.touchAction = 'auto'; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/ngx-chess-board-view.ts: -------------------------------------------------------------------------------- 1 | import { HistoryMove } from './history-move-provider/history-move'; 2 | import { ColorInput, PieceTypeInput } from './utils/inputs/piece-type-input'; 3 | 4 | export interface NgxChessBoardView { 5 | reset(): void; 6 | 7 | reverse(): void; 8 | 9 | undo(): void; 10 | 11 | getMoveHistory(): HistoryMove[]; 12 | 13 | setFEN(fen: string): void; 14 | 15 | move(coords: string): void; 16 | 17 | getFEN(): string; 18 | 19 | setPGN(pgn: string): void; 20 | 21 | addPiece(pieceTypeInput: PieceTypeInput, colorInput: ColorInput, coords: string); 22 | } 23 | -------------------------------------------------------------------------------- /src/app/components/actions/actions.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, EventEmitter, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-actions', 5 | templateUrl: './actions.component.html', 6 | styleUrls: ['./actions.component.scss'], 7 | changeDetection: ChangeDetectionStrategy.OnPush, 8 | }) 9 | export class ActionsComponent { 10 | @Output() public undo = new EventEmitter(); 11 | @Output() public reverse = new EventEmitter(); 12 | @Output() public restart = new EventEmitter(); 13 | @Output() public latest = new EventEmitter(); 14 | } 15 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/ngx-chess-board.module.ts: -------------------------------------------------------------------------------- 1 | import { DragDropModule } from '@angular/cdk/drag-drop'; 2 | import { CommonModule } from '@angular/common'; 3 | import { NgModule } from '@angular/core'; 4 | import { NgxChessBoardComponent } from './ngx-chess-board.component'; 5 | import { PiecePromotionModalComponent } from './piece-promotion/piece-promotion-modal/piece-promotion-modal.component'; 6 | 7 | @NgModule({ 8 | declarations: [NgxChessBoardComponent, PiecePromotionModalComponent], 9 | imports: [CommonModule, DragDropModule], 10 | exports: [NgxChessBoardComponent], 11 | }) 12 | export class NgxChessBoardModule {} 13 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/drawing-tools/colors/color-strategy.ts: -------------------------------------------------------------------------------- 1 | import { ColorProcessor } from './color-processor'; 2 | import { DefaultColorProcessor } from './default-color-processor'; 3 | 4 | export class ColorStrategy { 5 | 6 | colorProcessor: ColorProcessor; 7 | 8 | constructor() { 9 | this.colorProcessor = new DefaultColorProcessor(); 10 | } 11 | 12 | resolve(ctrl: any, shift: any, alt: any) { 13 | return this.colorProcessor.resolve(ctrl, shift, alt); 14 | } 15 | 16 | setColorProcessor(colorProcessor: ColorProcessor) { 17 | this.colorProcessor = colorProcessor; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "declarationMap": true, 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": [ 10 | "dom", 11 | "es2018" 12 | ] 13 | }, 14 | "angularCompilerOptions": { 15 | "skipTemplateCodegen": true, 16 | "strictMetadataEmit": true, 17 | "fullTemplateTypeCheck": true, 18 | "strictInjectionParameters": true, 19 | "enableResourceInlining": true 20 | }, 21 | "exclude": [ 22 | "src/test.ts", 23 | "**/*.spec.ts" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/ngx-chess-board.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { NgxChessBoardComponent } from './ngx-chess-board.component'; 4 | 5 | describe('NgxChessGameComponent', () => { 6 | let fixture: ComponentFixture; 7 | 8 | beforeEach(async(() => { 9 | TestBed.configureTestingModule({ 10 | declarations: [NgxChessBoardComponent], 11 | }).compileComponents(); 12 | })); 13 | 14 | beforeEach(() => { 15 | fixture = TestBed.createComponent(NgxChessBoardComponent); 16 | fixture.detectChanges(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-chess-board", 3 | "version": "3.0.1", 4 | "peerDependencies": { 5 | "@angular/common": "^17.0.9", 6 | "@angular/core": "^17.0.9", 7 | "@angular/cdk": "^17.0.5" 8 | }, 9 | "description": "Chess game component", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/grzegorz103/ngx-chess-board.git" 13 | }, 14 | "homepage": "https://github.com/grzegorz103/ngx-chess-board", 15 | "keywords": [ 16 | "angular", 17 | "chess", 18 | "game", 19 | "ng", 20 | "ngx", 21 | "player", 22 | "multiplayer", 23 | "board", 24 | "fen" 25 | ], 26 | "license": "MIT" 27 | } 28 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/history-move-provider/history-move.ts: -------------------------------------------------------------------------------- 1 | export class HistoryMove { 2 | move: string; 3 | piece: string; 4 | color: string; 5 | x: boolean; 6 | check: boolean; 7 | stalemate: boolean; 8 | mate: boolean; 9 | 10 | constructor(move: string, piece: string, color: string, captured: boolean) { 11 | this.move = move; 12 | this.piece = piece; 13 | this.color = color; 14 | this.x = captured; 15 | } 16 | 17 | setGameStates(check: boolean, stalemate: boolean, mate: boolean): void { 18 | this.check = check; 19 | this.stalemate = stalemate; 20 | this.mate = mate; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/public-api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of ngx-chess-board 3 | */ 4 | 5 | export * from './lib/ngx-chess-board.module'; 6 | export * from './lib/history-move-provider/history-move'; 7 | export * from './lib/utils/inputs/piece-icon-input'; 8 | 9 | export * from './lib/utils/inputs/piece-type-input'; 10 | export * from './lib/ngx-chess-board.component'; 11 | export * from './lib/piece-promotion/piece-promotion-modal/piece-promotion-modal.component'; 12 | 13 | export * from './lib/ngx-chess-board-view'; 14 | export * from './lib/models/board'; 15 | export * from './lib/engine/outputs/move-change/move-change' 16 | 17 | /* 18 | * Public API Surface of im-grid 19 | */ 20 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/models/pieces/point.ts: -------------------------------------------------------------------------------- 1 | export class Point { 2 | row: number; 3 | col: number; 4 | 5 | constructor(row: number, col: number) { 6 | this.row = row; 7 | this.col = col; 8 | } 9 | 10 | isEqual(that: Point) { 11 | return that && this.row === that.row && this.col === that.col; 12 | } 13 | 14 | hasCoordsEqual(row: number, col: number) { 15 | return row && col && this.row === row && this.col === col; 16 | } 17 | 18 | isInRange(): boolean { 19 | return this.row >= 0 && this.row <= 7 && this.col >= 0 && this.col <= 7; 20 | } 21 | 22 | clone() { 23 | return new Point(this.row, this.col); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('chess-board app is running!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | jobs: 4 | build: 5 | docker: 6 | - image: circleci/node:latest-browsers 7 | environment: 8 | CHROME_BIN: "/usr/bin/google-chrome" 9 | NG_CLI_ANALYTICS: "ci" 10 | steps: 11 | - checkout 12 | - run: 13 | name: Install node_modules with npm 14 | command: npm install 15 | - save_cache: 16 | key: dependency-cache-{{ checksum "package.json" }} 17 | paths: 18 | - ./node_modules 19 | - run: 20 | name: Install angularcli 21 | command: sudo npm install -g @angular/cli@latest > /dev/null 22 | 23 | - run: 24 | name: Run unit tests with karma 25 | command: ng test ngx-chess-board 26 | -------------------------------------------------------------------------------- /src/app/components/moves/moves.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { MovesComponent } from './moves.component'; 4 | 5 | describe('MovesComponent', () => { 6 | let component: MovesComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ MovesComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(MovesComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/components/moves/moves.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | {{ index + 1 }}. 5 | 10 | 11 |
12 |
13 |
14 |
15 | 21 | 22 |
23 | -------------------------------------------------------------------------------- /src/app/components/actions/actions.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ActionsComponent } from './actions.component'; 4 | 5 | describe('ActionsComponent', () => { 6 | let component: ActionsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ActionsComponent], 12 | }).compileComponents(); 13 | })); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(ActionsComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/drag/start/drag-start-strategy.ts: -------------------------------------------------------------------------------- 1 | import { CdkDragStart } from '@angular/cdk/drag-drop'; 2 | import { AnimationDragStartProcessor } from './animation-drag-start-processor'; 3 | import { DefaultDragStartProcessor } from './default-drag-start-processor'; 4 | import { DragStartProcessor } from './drag-start-processor'; 5 | 6 | export class DragStartStrategy { 7 | 8 | private dragStartProcessor: DragStartProcessor; 9 | 10 | constructor() { 11 | this.dragStartProcessor = new AnimationDragStartProcessor(); 12 | } 13 | 14 | public process(event: CdkDragStart): void { 15 | this.dragStartProcessor.dragStarted(event); 16 | } 17 | 18 | setDragStartProcessor(processor: DragStartProcessor) { 19 | this.dragStartProcessor = processor; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { FormsModule } from '@angular/forms'; 3 | import { BrowserModule } from '@angular/platform-browser'; 4 | import { NgxChessBoardModule } from 'ngx-chess-board'; 5 | import { AppComponent } from './app.component'; 6 | import { ActionsComponent } from './components/actions/actions.component'; 7 | import { FenComponent } from './components/fen/fen.component'; 8 | import { MovesComponent } from './components/moves/moves.component'; 9 | import { SettingsComponent } from './components/settings/settings.component'; 10 | 11 | @NgModule({ 12 | declarations: [AppComponent, ActionsComponent, SettingsComponent, MovesComponent, FenComponent], 13 | imports: [BrowserModule, FormsModule, NgxChessBoardModule], 14 | bootstrap: [AppComponent], 15 | }) 16 | export class AppModule {} 17 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/drag/end/drag-end-strategy.ts: -------------------------------------------------------------------------------- 1 | import { CdkDragEnd } from '@angular/cdk/drag-drop'; 2 | import { AnimationDragEndProcessor } from './animation-drag-end-processor'; 3 | import { DefaultDragEndProcessor } from './default-drag-end-processor'; 4 | import { DragEndProcessor } from './drag-end-processor'; 5 | 6 | export class DragEndStrategy { 7 | 8 | private dragEndProcessor: DragEndProcessor; 9 | 10 | constructor() { 11 | this.dragEndProcessor = new AnimationDragEndProcessor(); 12 | } 13 | 14 | public process(event: CdkDragEnd, disabling: boolean, startTrans: string): void { 15 | this.dragEndProcessor.dragEnded(event, disabling, startTrans); 16 | } 17 | 18 | setDragEndProcessor(processor: DragEndProcessor) { 19 | this.dragEndProcessor = processor; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | speed-measure-plugin*.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.angular/cache 36 | /.sass-cache 37 | /connect.lock 38 | /coverage 39 | /libpeerconnection.log 40 | npm-debug.log 41 | yarn-error.log 42 | testem.log 43 | /typings 44 | 45 | # System Files 46 | .DS_Store 47 | Thumbs.db 48 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/utils/unicode-constants.ts: -------------------------------------------------------------------------------- 1 | export interface PieceConstant { 2 | name: string; 3 | icon: string; 4 | } 5 | 6 | export const UnicodeConstants = { 7 | WHITE_KING: { name: 'King', icon: '♔' }, 8 | WHITE_QUEEN: { name: 'Queen', icon: '♕' }, 9 | WHITE_KNIGHT: { name: 'Knight', icon: '♘' }, 10 | WHITE_ROOK: { name: 'Rook', icon: '♖' }, 11 | WHITE_PAWN: { name: 'Pawn', icon: '♙' }, 12 | WHITE_BISHOP: { name: 'Bishop', icon: '♗' }, 13 | 14 | BLACK_KING: { name: 'King', icon: '♚' }, 15 | BLACK_QUEEN: { name: 'Queen', icon: '♛' }, 16 | BLACK_KNIGHT: { name: 'Knight', icon: '♞' }, 17 | BLACK_ROOK: { name: 'Rook', icon: '♜' }, 18 | BLACK_PAWN: { name: 'Pawn', icon: '♟' }, 19 | BLACK_BISHOP: { name: 'Bishop', icon: '♝' }, 20 | }; 21 | -------------------------------------------------------------------------------- /src/app/components/fen/fen.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { FormsModule } from '@angular/forms'; 3 | 4 | import { FenComponent } from './fen.component'; 5 | 6 | describe('FenComponent', () => { 7 | let component: FenComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async(() => { 11 | TestBed.configureTestingModule({ 12 | imports: [FormsModule], 13 | declarations: [FenComponent], 14 | }).compileComponents(); 15 | })); 16 | 17 | beforeEach(() => { 18 | fixture = TestBed.createComponent(FenComponent); 19 | component = fixture.componentInstance; 20 | fixture.detectChanges(); 21 | }); 22 | 23 | it('should create', () => { 24 | expect(component).toBeTruthy(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/models/move-translation.ts: -------------------------------------------------------------------------------- 1 | export class MoveTranslation { 2 | private _xAxis: number; 3 | private _yAxis: number; 4 | private _reverted: boolean; 5 | 6 | constructor(xAxis: number, yAxis: number, reverted: boolean) { 7 | this._xAxis = xAxis; 8 | this._yAxis = yAxis; 9 | this._reverted = reverted; 10 | } 11 | 12 | get xAxis(): number { 13 | return this._xAxis; 14 | } 15 | 16 | set xAxis(value: number) { 17 | this._xAxis = value; 18 | } 19 | 20 | get yAxis(): number { 21 | return this._yAxis; 22 | } 23 | 24 | set yAxis(value: number) { 25 | this._yAxis = value; 26 | } 27 | 28 | get reverted(): boolean { 29 | return this._reverted; 30 | } 31 | 32 | set reverted(value: boolean) { 33 | this._reverted = value; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/app/components/settings/settings.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { FormsModule } from '@angular/forms'; 3 | 4 | import { SettingsComponent } from './settings.component'; 5 | 6 | describe('SettingsComponent', () => { 7 | let component: SettingsComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async(() => { 11 | TestBed.configureTestingModule({ 12 | imports: [FormsModule], 13 | declarations: [SettingsComponent], 14 | }).compileComponents(); 15 | })); 16 | 17 | beforeEach(() => { 18 | fixture = TestBed.createComponent(SettingsComponent); 19 | component = fixture.componentInstance; 20 | fixture.detectChanges(); 21 | }); 22 | 23 | it('should create', () => { 24 | expect(component).toBeTruthy(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/board-state-provider/board-loader/notation-processors/notation-processor-factory.ts: -------------------------------------------------------------------------------- 1 | import { NotationProcessor } from './notation-processor'; 2 | import { 3 | DefaultFenProcessor, 4 | } from './fen-loader/default-fen-processor'; 5 | import { DefaultPgnProcessor } from './pgn-loader/default-pgn-processor'; 6 | 7 | export class NotationProcessorFactory { 8 | 9 | static getProcessor(type: NotationType): NotationProcessor { 10 | switch (type) { 11 | case NotationType.FEN: 12 | return new DefaultFenProcessor(); 13 | 14 | case NotationType.PGN: 15 | return new DefaultPgnProcessor(); 16 | 17 | } 18 | } 19 | 20 | static getDefaultProcessor(): NotationProcessor { 21 | return new DefaultFenProcessor(); 22 | } 23 | 24 | } 25 | 26 | export enum NotationType { 27 | FEN = 1, 28 | PGN = 2 29 | } 30 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "es2020", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "ES2022", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ], 21 | "paths": { 22 | "ngx-chess-board": [ 23 | "./projects/ngx-chess-board/src/public-api" 24 | ], 25 | "ngx-chess-board/*": [ 26 | "./projects/ngx-chess-board/src/public-api/*" 27 | ] 28 | }, 29 | "useDefineForClassFields": false 30 | }, 31 | "angularCompilerOptions": { 32 | "fullTemplateTypeCheck": true, 33 | "strictInjectionParameters": true 34 | } 35 | } -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | browserName: 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/piece-promotion/piece-promotion-modal/piece-promotion-modal.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { PiecePromotionModalComponent } from './piece-promotion-modal.component'; 4 | 5 | describe('PiecePromotionModalComponent', () => { 6 | let component: PiecePromotionModalComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [PiecePromotionModalComponent] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(PiecePromotionModalComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/coords/coords-provider.ts: -------------------------------------------------------------------------------- 1 | export class CoordsProvider { 2 | private readonly defaultXCoords: string[] = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; 3 | 4 | private readonly defaultYCoords: number[] = [8, 7, 6, 5, 4, 3, 2, 1]; 5 | 6 | private currentXCoords: string[] = [...this.defaultXCoords]; 7 | private currentYCoords: number[] = [...this.defaultYCoords]; 8 | 9 | get xCoords(): string[] { 10 | return this.currentXCoords; 11 | } 12 | 13 | get yCoords(): number[] { 14 | return this.currentYCoords; 15 | } 16 | 17 | reverse() { 18 | this.currentXCoords = this.currentXCoords.reverse(); 19 | this.currentYCoords = this.currentYCoords.reverse(); 20 | } 21 | 22 | reset() { 23 | this.init(); 24 | } 25 | 26 | private init() { 27 | this.currentXCoords = [...this.defaultXCoords]; 28 | this.currentYCoords = [...this.defaultYCoords]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 grzegorz103 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/models/pieces/piece.ts: -------------------------------------------------------------------------------- 1 | import { AbstractPiece } from '../../engine/piece-decorator/abstract-piece'; 2 | import { PieceConstant } from '../../utils/unicode-constants'; 3 | import { Board } from '../board'; 4 | import { Color } from './color'; 5 | import { Point } from './point'; 6 | 7 | export abstract class Piece implements AbstractPiece { 8 | point: Point; 9 | color: Color; 10 | constant: PieceConstant; 11 | checkPoints: Point[] = []; 12 | relValue: number; 13 | board: Board; 14 | 15 | constructor( 16 | point: Point, 17 | color: Color, 18 | constant: PieceConstant, 19 | relValue: number, 20 | board: Board 21 | ) { 22 | this.color = color; 23 | this.constant = constant; 24 | this.point = point; 25 | this.relValue = relValue; 26 | this.board = board; 27 | } 28 | 29 | abstract getPossibleMoves(): Point[]; 30 | 31 | abstract getPossibleCaptures(): Point[]; 32 | 33 | abstract getCoveredFields(): Point[]; // zwraca liste punktow ktore sa puste lub istnieje na nich pionek tego samego koloru 34 | } 35 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/chess-board'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/board-state-provider/board-state/board-state-provider.ts: -------------------------------------------------------------------------------- 1 | import { BehaviorSubject } from 'rxjs'; 2 | import { BoardState } from './board-state'; 3 | 4 | export class BoardStateProvider { 5 | statesSubject$ = new BehaviorSubject([]); 6 | 7 | get states(): BoardState[] { 8 | return this.statesSubject$.value; 9 | } 10 | 11 | set states(states: BoardState[]) { 12 | this.statesSubject$.next(states); 13 | } 14 | 15 | addMove(state: BoardState) { 16 | this.states = [...this.states, state]; 17 | } 18 | 19 | getStates(): BoardState[] { 20 | return this.states; 21 | } 22 | 23 | pop(): BoardState { 24 | const lastState = this.getLastState(); 25 | this.states = this.states.filter((state) => state !== lastState); 26 | return lastState; 27 | } 28 | 29 | isEmpty() { 30 | return this.states.length === 0; 31 | } 32 | 33 | clear() { 34 | this.states = []; 35 | } 36 | 37 | getLastState() { 38 | return this.states[this.getLastStateIndex()]; 39 | } 40 | 41 | getLastStateIndex(): number { 42 | return this.states.length - 1; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/board-state-provider/board-state/move-state-provider.ts: -------------------------------------------------------------------------------- 1 | import { BehaviorSubject } from 'rxjs'; 2 | import { BoardState } from './board-state'; 3 | 4 | export class MoveStateProvider { 5 | statesSubject$ = new BehaviorSubject([]); 6 | 7 | get states(): BoardState[] { 8 | return this.statesSubject$.value; 9 | } 10 | 11 | set states(states: BoardState[]) { 12 | this.statesSubject$.next(states); 13 | } 14 | 15 | addMove(state: BoardState) { 16 | this.states = [...this.states, state]; 17 | } 18 | 19 | getStates(): BoardState[] { 20 | return this.states; 21 | } 22 | 23 | pop(): BoardState { 24 | const lastState = this.getLastState(); 25 | this.states = this.states.filter((state) => state !== lastState); 26 | return lastState; 27 | } 28 | 29 | isEmpty() { 30 | return this.states.length === 0; 31 | } 32 | 33 | clear() { 34 | this.states = []; 35 | } 36 | 37 | getLastState() { 38 | return this.states[this.getLastStateIndex()]; 39 | } 40 | 41 | getLastStateIndex(): number { 42 | return this.states.length - 1; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/history-move-provider/history-move-provider.ts: -------------------------------------------------------------------------------- 1 | import { BehaviorSubject } from 'rxjs'; 2 | import { HistoryMove } from './history-move'; 3 | 4 | export class HistoryMoveProvider { 5 | historyMovesSubject$ = new BehaviorSubject([]); 6 | 7 | get historyMoves(): HistoryMove[] { 8 | return this.historyMovesSubject$.value; 9 | } 10 | 11 | set historyMoves(states: HistoryMove[]) { 12 | this.historyMovesSubject$.next(states); 13 | } 14 | 15 | addMove(historyMove: HistoryMove) { 16 | this.historyMoves = [...this.historyMoves, historyMove]; 17 | } 18 | 19 | pop(): HistoryMove { 20 | const lastHistoryMove = this.getLastMove(); 21 | this.historyMoves = this.historyMoves.filter( 22 | (state) => state !== lastHistoryMove 23 | ); 24 | return lastHistoryMove; 25 | } 26 | 27 | getAll() { 28 | return this.historyMoves; 29 | } 30 | 31 | clear() { 32 | this.historyMoves = []; 33 | } 34 | 35 | getLastMove() { 36 | return this.historyMoves[this.getLastMoveIndex()]; 37 | } 38 | 39 | getLastMoveIndex() { 40 | return this.historyMoves.length - 1; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../../coverage/ngx-chess-board'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: true, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /src/app/components/settings/settings.component.scss: -------------------------------------------------------------------------------- 1 | .switch { 2 | position: relative; 3 | display: inline-block; 4 | width: 50px; 5 | height: 24px; 6 | } 7 | 8 | /* Hide default HTML checkbox */ 9 | .switch input { 10 | opacity: 0; 11 | width: 0; 12 | height: 0; 13 | } 14 | 15 | /* The slider */ 16 | .slider { 17 | position: absolute; 18 | cursor: pointer; 19 | top: 0; 20 | left: 0; 21 | right: 0; 22 | bottom: 0; 23 | background-color: #ccc; 24 | -webkit-transition: 0.4s; 25 | transition: 0.4s; 26 | } 27 | 28 | .slider:before { 29 | position: absolute; 30 | content: ''; 31 | height: 16px; 32 | width: 16px; 33 | left: 6px; 34 | bottom: 4px; 35 | background-color: white; 36 | -webkit-transition: 0.4s; 37 | transition: 0.4s; 38 | } 39 | 40 | input:checked + .slider { 41 | background-color: #2196f3; 42 | } 43 | 44 | input:focus + .slider { 45 | box-shadow: 0 0 1px #2196f3; 46 | } 47 | 48 | input:checked + .slider:before { 49 | -webkit-transform: translateX(26px); 50 | -ms-transform: translateX(26px); 51 | transform: translateX(26px); 52 | } 53 | 54 | /* Rounded sliders */ 55 | .slider.round { 56 | border-radius: 34px; 57 | } 58 | 59 | .slider.round:before { 60 | border-radius: 50%; 61 | } 62 | 63 | .draggables { 64 | display: flex; 65 | flex-direction: column; 66 | } -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/piece-promotion/piece-promotion-modal/piece-promotion-modal.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | Queen 7 |
8 |
9 | Rook 10 |
11 |
12 | Bishop 13 |
14 |
15 | Knight 16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { DragDropModule } from '@angular/cdk/drag-drop'; 2 | import { async, TestBed } from '@angular/core/testing'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { NgxChessBoardComponent, PiecePromotionModalComponent } from 'ngx-chess-board'; 5 | import { AppComponent } from './app.component'; 6 | import { ActionsComponent } from './components/actions/actions.component'; 7 | import { FenComponent } from './components/fen/fen.component'; 8 | import { MovesComponent } from './components/moves/moves.component'; 9 | import { SettingsComponent } from './components/settings/settings.component'; 10 | 11 | describe('AppComponent', () => { 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | imports: [FormsModule, DragDropModule], 15 | declarations: [ 16 | AppComponent, 17 | SettingsComponent, 18 | ActionsComponent, 19 | NgxChessBoardComponent, 20 | MovesComponent, 21 | FenComponent, 22 | PiecePromotionModalComponent, 23 | ], 24 | }).compileComponents(); 25 | })); 26 | 27 | it('should create the app', () => { 28 | const fixture = TestBed.createComponent(AppComponent); 29 | const app = fixture.debugElement.componentInstance; 30 | expect(app).toBeTruthy(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/click/click-utils.ts: -------------------------------------------------------------------------------- 1 | import { Point } from '../../models/pieces/point'; 2 | import { ColorStrategy } from '../drawing-tools/colors/color-strategy'; 3 | import { DrawPoint } from '../drawing-tools/draw-point'; 4 | 5 | export class ClickUtils { 6 | 7 | static getClickPoint( 8 | event: any, 9 | top: number, 10 | height: number, 11 | left: number, 12 | width: number 13 | ) { 14 | return new Point( 15 | Math.floor((event.y - top) / (height / 8)), 16 | Math.floor((event.x - left) / (width / 8) 17 | ) 18 | ); 19 | } 20 | 21 | static getDrawingPoint( 22 | tileSize: number, 23 | colorStrategy: ColorStrategy, 24 | x: number, 25 | y: number, 26 | ctrl: boolean, 27 | alt: boolean, 28 | shift: boolean, 29 | xAxis: number, 30 | yAxis: number 31 | ) { 32 | const squareSize = tileSize / 8; 33 | const xx = Math.floor( 34 | (x - xAxis) / 35 | squareSize 36 | ); 37 | const yy = Math.floor( 38 | (y - yAxis) / 39 | squareSize 40 | ); 41 | 42 | let color = colorStrategy.resolve(ctrl, shift, alt); 43 | 44 | return new DrawPoint( 45 | Math.floor(xx * squareSize + squareSize / 2), 46 | Math.floor(yy * squareSize + squareSize / 2), 47 | color 48 | ); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: flex; 3 | justify-content: space-around; 4 | align-items: center; 5 | flex-wrap: wrap; 6 | height: 100%; 7 | } 8 | 9 | 10 | .switch { 11 | position: relative; 12 | display: inline-block; 13 | width: 50px; 14 | height: 24px; 15 | } 16 | 17 | /* Hide default HTML checkbox */ 18 | .switch input { 19 | opacity: 0; 20 | width: 0; 21 | height: 0; 22 | } 23 | 24 | /* The slider */ 25 | .slider { 26 | position: absolute; 27 | cursor: pointer; 28 | top: 0; 29 | left: 0; 30 | right: 0; 31 | bottom: 0; 32 | background-color: #ccc; 33 | -webkit-transition: 0.4s; 34 | transition: 0.4s; 35 | } 36 | 37 | .slider:before { 38 | position: absolute; 39 | content: ''; 40 | height: 16px; 41 | width: 16px; 42 | left: 6px; 43 | bottom: 4px; 44 | background-color: white; 45 | -webkit-transition: 0.4s; 46 | transition: 0.4s; 47 | } 48 | 49 | input:checked + .slider { 50 | background-color: #2196f3; 51 | } 52 | 53 | input:focus + .slider { 54 | box-shadow: 0 0 1px #2196f3; 55 | } 56 | 57 | input:checked + .slider:before { 58 | -webkit-transform: translateX(26px); 59 | -ms-transform: translateX(26px); 60 | transform: translateX(26px); 61 | } 62 | 63 | /* Rounded sliders */ 64 | .slider.round { 65 | border-radius: 34px; 66 | } 67 | 68 | .slider.round:before { 69 | border-radius: 50%; 70 | } 71 | 72 | .draggables { 73 | display: flex; 74 | flex-direction: column; 75 | } 76 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/piece-promotion/piece-promotion-modal/piece-promotion-modal.component.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | display: none; 3 | position: absolute; 4 | z-index: 9999; 5 | top: 0; 6 | color: black; 7 | width: 100%; 8 | height: 100%; 9 | overflow: auto; 10 | background-color: rgba(0, 0, 0, 0.4); /* Black w/ opacity */ 11 | } 12 | 13 | .wrapper { 14 | position: relative; 15 | height: 100%; 16 | width: 100%; 17 | } 18 | 19 | .content { 20 | background-color: #fefefe; 21 | margin: auto; 22 | position: relative; 23 | top: 30%; 24 | font-size: 100%; 25 | height: 40%; 26 | padding: 10px; 27 | border: 1px solid #888; 28 | width: 90%; 29 | } 30 | 31 | .piece { 32 | font-size: 5rem; 33 | height: 100%; 34 | width: 25%; 35 | cursor: pointer; 36 | display: inline-block; 37 | text-align: center; 38 | 39 | img { 40 | max-width: 100%; 41 | } 42 | } 43 | 44 | .piece:hover{ 45 | background-color: #cccccc; 46 | border-radius: 5px; 47 | } 48 | 49 | .piece-wrapper { 50 | height: 80%; 51 | width: 100%; 52 | } 53 | 54 | 55 | #close-button { 56 | border-radius: 4px; 57 | background-color: #4CAF50; 58 | border: none; 59 | color: white; 60 | padding-left: 5px; 61 | padding-right: 5px; 62 | text-align: center; 63 | text-decoration: none; 64 | display: inline-block; 65 | } 66 | 67 | .selected{ 68 | border:2px solid #00B919; 69 | border-radius: 4px; 70 | box-sizing: border-box; 71 | } 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "npm run build-board && ng build --configuration production", 8 | "build-board": "ng build ngx-chess-board", 9 | "test": "ng test", 10 | "lint": "ng lint", 11 | "e2e": "ng e2e" 12 | }, 13 | "dependencies": { 14 | "@angular/animations": "^17.0.9", 15 | "@angular/cdk": "^17.0.5", 16 | "@angular/common": "^17.0.9", 17 | "@angular/compiler": "^17.0.9", 18 | "@angular/core": "^17.0.9", 19 | "@angular/forms": "^17.0.9", 20 | "@angular/platform-browser": "^17.0.9", 21 | "@angular/platform-browser-dynamic": "^17.0.9", 22 | "@angular/router": "^17.0.9", 23 | "@types/lodash": "^4.14.202", 24 | "bootstrap": "^5.3.2", 25 | "rxjs": "~7.8.1", 26 | "tslib": "^2.6.2", 27 | "zone.js": "~0.14.3" 28 | }, 29 | "devDependencies": { 30 | "@angular-devkit/build-angular": "^17.0.10", 31 | "@angular/cli": "^17.0.10", 32 | "@angular/compiler-cli": "^17.0.9", 33 | "@angular/language-service": "^17.0.9", 34 | "@types/jasmine": "~5.1.4", 35 | "@types/jasminewd2": "~2.0.13", 36 | "@types/node": "^20.11.0", 37 | "codelyzer": "^6.0.2", 38 | "jasmine-core": "~5.1.1", 39 | "jasmine-spec-reporter": "~7.0.0", 40 | "karma": "~6.4.2", 41 | "karma-chrome-launcher": "~3.2.0", 42 | "karma-coverage-istanbul-reporter": "~3.0.3", 43 | "karma-jasmine": "~5.1.0", 44 | "karma-jasmine-html-reporter": "^2.1.0", 45 | "ng-packagr": "^17.0.3", 46 | "protractor": "~7.0.0", 47 | "ts-node": "~10.9.2", 48 | "tslint": "~6.1.3", 49 | "typescript": "~5.2.2" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/drawing-tools/draw-provider.ts: -------------------------------------------------------------------------------- 1 | import { BehaviorSubject } from 'rxjs'; 2 | import { Arrow } from './shapes/arrow'; 3 | import { Circle } from './shapes/circle'; 4 | 5 | export class DrawProvider { 6 | private arrowsSubject$ = new BehaviorSubject([]); 7 | private circlesSubject$ = new BehaviorSubject([]); 8 | 9 | public arrows$ = this.arrowsSubject$.asObservable(); 10 | public circles$ = this.circlesSubject$.asObservable(); 11 | 12 | private get circles(): Circle[] { 13 | return this.circlesSubject$.value; 14 | } 15 | 16 | private set circles(circles: Circle[]) { 17 | this.circlesSubject$.next(circles); 18 | } 19 | 20 | private get arrows(): Arrow[] { 21 | return this.arrowsSubject$.value; 22 | } 23 | 24 | private set arrows(arrows: Arrow[]) { 25 | this.arrowsSubject$.next(arrows); 26 | } 27 | 28 | addCircle(circle: Circle) { 29 | this.circles = [...this.circles, circle]; 30 | } 31 | 32 | reomveCircle(removeCircle: Circle) { 33 | this.circles = this.circles.filter((circle) => !circle.isEqual(removeCircle)); 34 | } 35 | 36 | addArrow(arrow: Arrow) { 37 | this.arrows = [...this.arrows, arrow]; 38 | } 39 | 40 | removeArrow(removeArrow: Arrow) { 41 | this.arrows = this.arrows.filter((arrow) => !arrow.isEqual(removeArrow)); 42 | } 43 | 44 | containsCircle(checkCircle: Circle) { 45 | return this.circles.some((circle) => circle.isEqual(checkCircle)); 46 | } 47 | 48 | containsArrow(checkArrow: Arrow) { 49 | return this.arrows.some((arrow: Arrow) => arrow.isEqual(checkArrow)); 50 | } 51 | 52 | clear() { 53 | this.arrows = []; 54 | this.circles = []; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/pgn/abstract-pgn-processor.ts: -------------------------------------------------------------------------------- 1 | import { Board } from '../../models/board'; 2 | import { Piece } from '../../models/pieces/piece'; 3 | import { Point } from '../../models/pieces/point'; 4 | 5 | export abstract class AbstractPgnProcessor { 6 | 7 | protected pgn = []; 8 | protected currentIndex = 0.5; 9 | 10 | public abstract process( 11 | board: Board, 12 | sourcePiece: Piece, 13 | destPoint: Point, 14 | destPiece?: Piece 15 | ): void; 16 | 17 | public getPGN() { 18 | return this.pgn.join(' '); 19 | } 20 | 21 | protected getLast() { 22 | return this.pgn[this.pgn.length - 1]; 23 | } 24 | 25 | protected appendToLast(str: string) { 26 | this.pgn[this.pgn.length - 1] = this.getLast() + str; 27 | } 28 | 29 | processChecks(checkmate: boolean, check: boolean, stalemate: boolean) { 30 | if (checkmate) { 31 | this.appendToLast('#'); 32 | } else { 33 | if (check) { 34 | this.appendToLast('+'); 35 | } 36 | } 37 | } 38 | 39 | reset() { 40 | this.pgn = []; 41 | this.currentIndex = 0.5; 42 | } 43 | 44 | addPromotionChoice(promotion) { 45 | switch (promotion) { 46 | case 1: 47 | this.appendToLast('=Q'); 48 | break; 49 | case 2: 50 | this.appendToLast('=R'); 51 | break; 52 | case 3: 53 | this.appendToLast('=B'); 54 | break; 55 | case 4: 56 | this.appendToLast('=N'); 57 | break; 58 | } 59 | } 60 | 61 | removeLast() { 62 | this.pgn.pop(); 63 | this.currentIndex -= 0.5; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/piece-decorator/available-move-decorator.ts: -------------------------------------------------------------------------------- 1 | import { Board } from '../../models/board'; 2 | import { Color } from '../../models/pieces/color'; 3 | import { Point } from '../../models/pieces/point'; 4 | import { MoveUtils } from '../../utils/move-utils'; 5 | import { AbstractPiece } from './abstract-piece'; 6 | import { PieceAbstractDecorator } from './piece-abstract-decorator'; 7 | 8 | export class AvailableMoveDecorator extends PieceAbstractDecorator { 9 | private pointClicked: Point; 10 | private color: Color; 11 | private board: Board; 12 | 13 | constructor(piece: AbstractPiece, pointClicked: Point, color: Color, board: Board) { 14 | super(piece); 15 | this.pointClicked = pointClicked; 16 | this.color = color; 17 | this.board = board; 18 | } 19 | 20 | getPossibleCaptures(): Point[] { 21 | return this.piece 22 | .getPossibleCaptures() 23 | .filter( 24 | (point) => 25 | !MoveUtils.willMoveCauseCheck( 26 | this.color, 27 | this.pointClicked.row, 28 | this.pointClicked.col, 29 | point.row, 30 | point.col, 31 | this.board 32 | ) 33 | ); 34 | } 35 | 36 | getPossibleMoves(): Point[] { 37 | return this.piece 38 | .getPossibleMoves() 39 | .filter( 40 | (point) => 41 | !MoveUtils.willMoveCauseCheck( 42 | this.color, 43 | this.pointClicked.row, 44 | this.pointClicked.col, 45 | point.row, 46 | point.col, 47 | this.board 48 | ) 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/piece-promotion/piece-promotion-modal/piece-promotion-modal.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, Input, ViewChild } from '@angular/core'; 2 | import { PieceIconInput } from '../../utils/inputs/piece-icon-input'; 3 | 4 | @Component({ 5 | selector: 'app-piece-promotion-modal', 6 | templateUrl: './piece-promotion-modal.component.html', 7 | styleUrls: ['./piece-promotion-modal.component.scss'] 8 | }) 9 | export class PiecePromotionModalComponent { 10 | 11 | @ViewChild('myModal', {static: false}) modal: ElementRef; 12 | 13 | @Input() 14 | pieceIconInput: PieceIconInput; 15 | 16 | @Input() 17 | color = 'white'; 18 | 19 | opened = false; 20 | private onCloseCallback: (index: number) => void; 21 | 22 | open(closeCallback: (index: number) => void) { 23 | this.opened = true; 24 | this.onCloseCallback = closeCallback; 25 | this.modal.nativeElement.style.display = 'block'; 26 | } 27 | 28 | changeSelection(index: number){ 29 | this.modal.nativeElement.style.display = 'none'; 30 | this.opened = false; 31 | this.onCloseCallback(index); 32 | } 33 | 34 | getPieceIcon(piece: string): string { 35 | let coloredPiece = ''; 36 | switch (piece.toLowerCase()) { 37 | case 'queen': 38 | coloredPiece = this.color === 'white' ? this.pieceIconInput.whiteQueenUrl : this.pieceIconInput.blackQueenUrl; 39 | break; 40 | case 'rook': 41 | coloredPiece = this.color === 'white' ? this.pieceIconInput.whiteRookUrl : this.pieceIconInput.blackRookUrl; 42 | break; 43 | case 'bishop': 44 | coloredPiece = this.color === 'white' ? this.pieceIconInput.whiteBishopUrl : this.pieceIconInput.blackBishopUrl; 45 | break; 46 | case 'knight': 47 | coloredPiece = this.color === 'white' ? this.pieceIconInput.whiteKnightUrl : this.pieceIconInput.blackKnightUrl; 48 | break; 49 | } 50 | 51 | return coloredPiece; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/app/components/moves/moves.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, EventEmitter, Output } from '@angular/core'; 2 | import { MoveChange } from 'ngx-chess-board'; 3 | import { BehaviorSubject } from 'rxjs'; 4 | 5 | @Component({ 6 | selector: 'app-moves', 7 | templateUrl: './moves.component.html', 8 | styleUrls: ['./moves.component.scss'], 9 | changeDetection: ChangeDetectionStrategy.OnPush, 10 | }) 11 | export class MovesComponent { 12 | @Output() public switchToMoveIndex = new EventEmitter(); 13 | 14 | private whiteMovesSubject$: BehaviorSubject = new BehaviorSubject([]); 15 | private blackMovesSubject$: BehaviorSubject = new BehaviorSubject([]); 16 | 17 | public whiteMoves$ = this.whiteMovesSubject$.asObservable(); 18 | public blackMoves$ = this.blackMovesSubject$.asObservable(); 19 | 20 | public undo() { 21 | if (this.whiteMoves.length > this.blackMoves.length) { 22 | this.whiteMoves = this.whiteMoves.filter((_, index) => index < this.whiteMoves.length - 1); 23 | } else { 24 | this.blackMoves = this.blackMoves.filter((_, index) => index < this.blackMoves.length - 1); 25 | } 26 | } 27 | 28 | private get whiteMoves(): string[] { 29 | return this.whiteMovesSubject$.value; 30 | } 31 | 32 | private set whiteMoves(moves: string[]) { 33 | this.whiteMovesSubject$.next(moves); 34 | } 35 | 36 | private get blackMoves(): string[] { 37 | return this.blackMovesSubject$.value; 38 | } 39 | 40 | private set blackMoves(moves: string[]) { 41 | this.blackMovesSubject$.next(moves); 42 | } 43 | 44 | public clear() { 45 | this.whiteMoves = []; 46 | this.blackMoves = []; 47 | } 48 | 49 | public addWhiteMove(move: string) { 50 | this.whiteMoves = [...this.whiteMoves, move]; 51 | } 52 | 53 | public addBlackMove(move: string) { 54 | this.blackMoves = [...this.blackMoves, move]; 55 | } 56 | 57 | public addMove(historyMove: MoveChange): void { 58 | 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/board-state-provider/board-loader/board-loader.ts: -------------------------------------------------------------------------------- 1 | import { Board } from '../../../models/board'; 2 | import { Bishop } from '../../../models/pieces/bishop'; 3 | import { Color } from '../../../models/pieces/color'; 4 | import { King } from '../../../models/pieces/king'; 5 | import { Knight } from '../../../models/pieces/knight'; 6 | import { Pawn } from '../../../models/pieces/pawn'; 7 | import { Point } from '../../../models/pieces/point'; 8 | import { Queen } from '../../../models/pieces/queen'; 9 | import { Rook } from '../../../models/pieces/rook'; 10 | import { UnicodeConstants } from '../../../utils/unicode-constants'; 11 | import { AbstractEngineFacade } from '../../abstract-engine-facade'; 12 | import { DefaultPiecesLoader } from './default-pieces-loader'; 13 | import { NotationProcessor } from './notation-processors/notation-processor'; 14 | import { NotationProcessorFactory } from './notation-processors/notation-processor-factory'; 15 | 16 | export class BoardLoader { 17 | 18 | private engineFacade: AbstractEngineFacade; 19 | private notationProcessor: NotationProcessor; 20 | 21 | constructor(engineFacade: AbstractEngineFacade, notationProcessor?: NotationProcessor) { 22 | this.engineFacade = engineFacade; 23 | 24 | if (notationProcessor) { 25 | this.notationProcessor = notationProcessor; 26 | } else { 27 | this.notationProcessor = NotationProcessorFactory.getDefaultProcessor(); 28 | } 29 | 30 | } 31 | 32 | addPieces() { 33 | DefaultPiecesLoader.loadDefaultPieces(this.engineFacade.board); 34 | } 35 | 36 | loadFEN(fen: string) { 37 | this.notationProcessor.process(fen, this.engineFacade); 38 | } 39 | 40 | loadPGN(pgn: string) { 41 | this.notationProcessor.process(pgn, this.engineFacade) 42 | } 43 | 44 | setEngineFacade(engineFacade: AbstractEngineFacade) { 45 | this.engineFacade = engineFacade; 46 | } 47 | 48 | setNotationProcessor(notationProcessor: NotationProcessor) { 49 | this.notationProcessor = notationProcessor; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags.ts'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | 51 | /*************************************************************************************************** 52 | * APPLICATION IMPORTS 53 | */ 54 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/piece-promotion/piece-promotion-resolver.ts: -------------------------------------------------------------------------------- 1 | import { Board } from '../models/board'; 2 | import { Bishop } from '../models/pieces/bishop'; 3 | import { Color } from '../models/pieces/color'; 4 | import { Knight } from '../models/pieces/knight'; 5 | import { Piece } from '../models/pieces/piece'; 6 | import { Queen } from '../models/pieces/queen'; 7 | import { Rook } from '../models/pieces/rook'; 8 | import { UnicodeConstants } from '../utils/unicode-constants'; 9 | 10 | export class PiecePromotionResolver { 11 | 12 | static resolvePromotionChoice(board: Board, piece: Piece, index: number) { 13 | const isWhite = piece.color === Color.WHITE; 14 | switch (index) { 15 | case 1: 16 | board.pieces.push( 17 | new Queen( 18 | piece.point, 19 | piece.color, 20 | isWhite 21 | ? UnicodeConstants.WHITE_QUEEN 22 | : UnicodeConstants.BLACK_QUEEN, 23 | board 24 | ) 25 | ); 26 | break; 27 | case 2: 28 | board.pieces.push( 29 | new Rook( 30 | piece.point, 31 | piece.color, 32 | isWhite 33 | ? UnicodeConstants.WHITE_ROOK 34 | : UnicodeConstants.BLACK_ROOK, 35 | board 36 | ) 37 | ); 38 | break; 39 | case 3: 40 | board.pieces.push( 41 | new Bishop( 42 | piece.point, 43 | piece.color, 44 | isWhite 45 | ? UnicodeConstants.WHITE_BISHOP 46 | : UnicodeConstants.BLACK_BISHOP, 47 | board 48 | ) 49 | ); 50 | break; 51 | case 4: 52 | board.pieces.push( 53 | new Knight( 54 | piece.point, 55 | piece.color, 56 | isWhite 57 | ? UnicodeConstants.WHITE_KNIGHT 58 | : UnicodeConstants.BLACK_KNIGHT, 59 | board 60 | ) 61 | ); 62 | break; 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/utils/inputs/piece-icon-input-manager.ts: -------------------------------------------------------------------------------- 1 | import { Bishop } from '../../models/pieces/bishop'; 2 | import { Color } from '../../models/pieces/color'; 3 | import { King } from '../../models/pieces/king'; 4 | import { Knight } from '../../models/pieces/knight'; 5 | import { Pawn } from '../../models/pieces/pawn'; 6 | import { Piece } from '../../models/pieces/piece'; 7 | import { Queen } from '../../models/pieces/queen'; 8 | import { Rook } from '../../models/pieces/rook'; 9 | import { PieceIconInput } from './piece-icon-input'; 10 | 11 | export class PieceIconInputManager { 12 | 13 | private _defaultIcons: boolean = false; 14 | private _pieceIconInput: PieceIconInput; 15 | 16 | get pieceIconInput(): PieceIconInput { 17 | return this._pieceIconInput; 18 | } 19 | 20 | set pieceIconInput(value: PieceIconInput) { 21 | this._pieceIconInput = value; 22 | } 23 | 24 | 25 | get defaultIcons(): boolean { 26 | return this._defaultIcons; 27 | } 28 | 29 | set defaultIcons(value: boolean) { 30 | this._defaultIcons = value; 31 | } 32 | 33 | isDefaultIcons(): boolean { 34 | return this.pieceIconInput === undefined || this.pieceIconInput === null; 35 | } 36 | 37 | getPieceIcon(piece: Piece): string { 38 | let isWhite = (piece.color === Color.WHITE); 39 | switch (piece.constructor) { 40 | case King: 41 | return isWhite ? this.pieceIconInput.whiteKingUrl : this.pieceIconInput.blackKingUrl; 42 | case Queen: 43 | return isWhite ? this.pieceIconInput.whiteQueenUrl : this.pieceIconInput.blackQueenUrl; 44 | case Rook: 45 | return isWhite ? this.pieceIconInput.whiteRookUrl : this.pieceIconInput.blackRookUrl; 46 | case Bishop: 47 | return isWhite ? this.pieceIconInput.whiteBishopUrl : this.pieceIconInput.blackBishopUrl; 48 | case Knight: 49 | return isWhite ? this.pieceIconInput.whiteKnightUrl : this.pieceIconInput.blackKnightUrl; 50 | case Pawn: 51 | return isWhite ? this.pieceIconInput.whitePawnUrl : this.pieceIconInput.blackPawnUrl; 52 | } 53 | } 54 | 55 | loadDefaultData(){ 56 | this.pieceIconInput = { 57 | blackBishopUrl: '', 58 | blackKingUrl: '', 59 | blackKnightUrl: '', 60 | blackQueenUrl: '', 61 | blackRookUrl: '', 62 | whiteBishopUrl: '', 63 | whiteKingUrl: '', 64 | whiteKnightUrl: '', 65 | whitePawnUrl: '', 66 | whiteQueenUrl: '', 67 | whiteRookUrl: '', 68 | blackPawnUrl: 'a' 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/app/components/settings/settings.component.html: -------------------------------------------------------------------------------- 1 | 103 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/ngx-chess-board.component.scss: -------------------------------------------------------------------------------- 1 | #board { 2 | font-family: 'Courier New', serif; 3 | position: relative; 4 | } 5 | 6 | .board-row { 7 | display: block; 8 | width: 100%; 9 | height: 12.5%; 10 | position: relative; 11 | } 12 | 13 | .board-col { 14 | position: relative; 15 | display: inline-block; 16 | width: 12.5%; 17 | vertical-align: top; 18 | cursor: default; 19 | height: 100%; 20 | } 21 | 22 | .piece { 23 | height: 100%; 24 | cursor: grab; 25 | width: 100%; 26 | user-select: none; 27 | -webkit-user-select: none; 28 | background-size: cover; 29 | justify-content: center; 30 | text-align: center; 31 | //margin-top: -10px; 32 | color: black !important; 33 | box-sizing: border-box; 34 | } 35 | 36 | .piece::after { 37 | content: '\200b'; 38 | box-sizing: border-box; 39 | } 40 | 41 | #drag { 42 | height: 100%; 43 | // border: 3px solid blue; 44 | width: 100%; 45 | } 46 | 47 | .possible-point { 48 | background: radial-gradient(#13262F 15%, transparent 20%); 49 | } 50 | 51 | .possible-point:hover, 52 | .possible-capture:hover { 53 | opacity: 0.4; 54 | } 55 | 56 | .possible-capture { 57 | background: radial-gradient(transparent 0%, transparent 80%,#13262F 80%); 58 | opacity: 0.5; 59 | box-sizing: border-box; margin: 0; 60 | padding: 0; 61 | } 62 | 63 | .king-check { 64 | background: radial-gradient( 65 | ellipse at center, 66 | red 0%, 67 | #e70000 25%, 68 | rgba(169, 0, 0, 0) 89%, 69 | rgba(158, 0, 0, 0) 100% 70 | ); 71 | } 72 | 73 | .current-selection { 74 | background-color: #72620B !important; 75 | } 76 | 77 | .yCoord { 78 | position: absolute; 79 | user-select: none; 80 | -webkit-user-select: none; 81 | cursor: pointer; 82 | right: 0.2em; 83 | font-family: 'Lucida Console', Courier, monospace; 84 | box-sizing: border-box; 85 | } 86 | 87 | .xCoord { 88 | position: absolute; 89 | user-select: none; 90 | -webkit-user-select: none; 91 | cursor: pointer; 92 | left: 0.2em; 93 | bottom: 0; 94 | font-family: 'Lucida Console', Courier, monospace; 95 | box-sizing: border-box; 96 | } 97 | 98 | .hovering { 99 | background-color: red !important; 100 | } 101 | 102 | .arrow { 103 | stroke-width: 2; 104 | } 105 | 106 | svg { 107 | filter: drop-shadow(1px 1px 0px #111) drop-shadow(-1px 1px 0px #111) drop-shadow(1px -1px 0px #111) 108 | drop-shadow(-1px -1px 0px #111); 109 | } 110 | 111 | :host { display:inline-block; } 112 | 113 | .single-piece { 114 | position: absolute; 115 | z-index: 999; 116 | justify-content: center; 117 | text-align: center; 118 | user-select: none; 119 | -webkit-user-select: none; 120 | //-webkit-transition: all 1s ease-in-out; 121 | //-moz-transition: all 1s ease-in-out; 122 | //-o-transition: all 1s ease-in-out; 123 | //-ms-transition: all 1s ease-in-out; 124 | color: black !important; 125 | cursor: grab; 126 | background-size: cover; 127 | } 128 | 129 | .single-piece::after { 130 | content: '\200b'; 131 | box-sizing: border-box; 132 | } 133 | .cdk-drag:not(.cdk-drag-dragging) { 134 | transition: transform var(--animation-duration) cubic-bezier(0,.3,.14,.49); 135 | } 136 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/utils/piece-factory.ts: -------------------------------------------------------------------------------- 1 | import { Board } from '../../models/board'; 2 | import { MoveTranslation } from '../../models/move-translation'; 3 | import { Bishop } from '../../models/pieces/bishop'; 4 | import { Color } from '../../models/pieces/color'; 5 | import { King } from '../../models/pieces/king'; 6 | import { Knight } from '../../models/pieces/knight'; 7 | import { Pawn } from '../../models/pieces/pawn'; 8 | import { Piece } from '../../models/pieces/piece'; 9 | import { Point } from '../../models/pieces/point'; 10 | import { Queen } from '../../models/pieces/queen'; 11 | import { Rook } from '../../models/pieces/rook'; 12 | import { 13 | ColorInput, 14 | PieceTypeInput 15 | } from '../../utils/inputs/piece-type-input'; 16 | import { UnicodeConstants } from '../../utils/unicode-constants'; 17 | 18 | export class PieceFactory { 19 | 20 | static create( 21 | indexes: MoveTranslation, 22 | pieceTypeInput: PieceTypeInput, 23 | colorInput: ColorInput, 24 | board: Board 25 | ): Piece { 26 | let piece; 27 | let color = colorInput === ColorInput.LIGHT 28 | ? Color.WHITE 29 | : Color.BLACK; 30 | 31 | switch (pieceTypeInput) { 32 | case PieceTypeInput.QUEEN: 33 | piece = new Queen( 34 | new Point(indexes.yAxis, indexes.xAxis), 35 | color, 36 | color === Color.WHITE ? UnicodeConstants.WHITE_QUEEN : UnicodeConstants.BLACK_QUEEN, 37 | board 38 | ); 39 | break; 40 | case PieceTypeInput.KING: 41 | piece = new King( 42 | new Point(indexes.yAxis, indexes.xAxis), 43 | color, 44 | color === Color.WHITE ? UnicodeConstants.WHITE_KING : UnicodeConstants.BLACK_KING, 45 | board 46 | ); 47 | 48 | break; 49 | case PieceTypeInput.KNIGHT: 50 | piece = new Knight( 51 | new Point(indexes.yAxis, indexes.xAxis), 52 | color, 53 | color === Color.WHITE ? UnicodeConstants.WHITE_KNIGHT : UnicodeConstants.BLACK_KNIGHT, 54 | board 55 | ); 56 | break; 57 | case PieceTypeInput.BISHOP: 58 | piece = new Bishop( 59 | new Point(indexes.yAxis, indexes.xAxis), 60 | color, 61 | color === Color.WHITE ? UnicodeConstants.WHITE_BISHOP : UnicodeConstants.BLACK_BISHOP, 62 | board 63 | ); 64 | break; 65 | case PieceTypeInput.ROOK: 66 | piece = new Rook( 67 | new Point(indexes.yAxis, indexes.xAxis), 68 | color, 69 | color === Color.WHITE ? UnicodeConstants.WHITE_ROOK : UnicodeConstants.BLACK_ROOK, 70 | board 71 | ); 72 | break; 73 | case PieceTypeInput.PAWN: 74 | piece = new Pawn( 75 | new Point(indexes.yAxis, indexes.xAxis), 76 | color, 77 | color === Color.WHITE ? UnicodeConstants.WHITE_PAWN : UnicodeConstants.BLACK_PAWN, 78 | board 79 | ); 80 | break; 81 | } 82 | 83 | return piece; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | 5 | "array-type": false, 6 | "arrow-parens": false, 7 | "arrow-return-shorthand": true, 8 | "deprecation": { 9 | "severity": "warning" 10 | }, 11 | "component-class-suffix": true, 12 | "contextual-lifecycle": true, 13 | "curly": true, 14 | "directive-class-suffix": true, 15 | "directive-selector": [ 16 | true, 17 | "attribute", 18 | "app", 19 | "camelCase" 20 | ], 21 | "component-selector": [ 22 | true, 23 | "element", 24 | "app", 25 | "kebab-case" 26 | ], 27 | "import-blacklist": [ 28 | true, 29 | "rxjs/Rx" 30 | ], 31 | "import-spacing": true, 32 | "indent": { 33 | "options": [ 34 | "spaces" 35 | ] 36 | }, 37 | "interface-name": false, 38 | "max-classes-per-file": false, 39 | "max-line-length": [ 40 | true, 41 | 350 42 | ], 43 | "member-access": false, 44 | "member-ordering": [ 45 | true, 46 | { 47 | "order": [ 48 | "static-field", 49 | "instance-field", 50 | "static-method", 51 | "instance-method" 52 | ] 53 | } 54 | ], 55 | "no-consecutive-blank-lines": false, 56 | "no-console": [ 57 | true, 58 | "debug", 59 | "info", 60 | "time", 61 | "timeEnd", 62 | "trace" 63 | ], 64 | "no-empty": false, 65 | "no-inferrable-types": [ 66 | true, 67 | "ignore-params" 68 | ], 69 | "no-non-null-assertion": true, 70 | "no-redundant-jsdoc": true, 71 | "no-switch-case-fall-through": true, 72 | "no-var-requires": false, 73 | "object-literal-key-quotes": [ 74 | true, 75 | "as-needed" 76 | ], 77 | "object-literal-sort-keys": false, 78 | "ordered-imports": false, 79 | "quotemark": [ 80 | true, 81 | "single" 82 | ], 83 | "trailing-comma": false, 84 | "no-conflicting-lifecycle": true, 85 | "no-host-metadata-property": true, 86 | "no-input-rename": true, 87 | "no-inputs-metadata-property": true, 88 | "no-output-native": true, 89 | "no-output-on-prefix": true, 90 | "no-output-rename": true, 91 | "semicolon": { 92 | "options": [ 93 | "always" 94 | ] 95 | }, 96 | "space-before-function-paren": { 97 | "options": { 98 | "anonymous": "never", 99 | "asyncArrow": "always", 100 | "constructor": "never", 101 | "method": "never", 102 | "named": "never" 103 | } 104 | }, 105 | "no-outputs-metadata-property": true, 106 | "template-banana-in-box": true, 107 | "template-no-negated-async": true, 108 | "typedef-whitespace": { 109 | "options": [ 110 | { 111 | "call-signature": "nospace", 112 | "index-signature": "nospace", 113 | "parameter": "nospace", 114 | "property-declaration": "nospace", 115 | "variable-declaration": "nospace" 116 | }, 117 | { 118 | "call-signature": "onespace", 119 | "index-signature": "onespace", 120 | "parameter": "onespace", 121 | "property-declaration": "onespace", 122 | "variable-declaration": "onespace" 123 | } 124 | ] 125 | }, 126 | "use-lifecycle-interface": true, 127 | "use-pipe-transform-interface": true, 128 | "variable-name": { 129 | "options": [ 130 | "ban-keywords", 131 | "check-format", 132 | "allow-pascal-case" 133 | ] 134 | }, 135 | "whitespace": { 136 | "options": [ 137 | "check-branch", 138 | "check-decl", 139 | "check-operator", 140 | "check-separator", 141 | "check-type", 142 | "check-typecast" 143 | ] 144 | } 145 | }, 146 | "rulesDirectory": [ 147 | "codelyzer" 148 | ] 149 | } 150 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/abstract-engine-facade.ts: -------------------------------------------------------------------------------- 1 | import { PiecePromotionModalComponent } from '../piece-promotion/piece-promotion-modal/piece-promotion-modal.component'; 2 | import { HistoryMove } from '../history-move-provider/history-move'; 3 | import { HistoryMoveProvider } from '../history-move-provider/history-move-provider'; 4 | import { Board } from '../models/board'; 5 | import { King } from '../models/pieces/king'; 6 | import { Pawn } from '../models/pieces/pawn'; 7 | import { Piece } from '../models/pieces/piece'; 8 | import { Point } from '../models/pieces/point'; 9 | import { Rook } from '../models/pieces/rook'; 10 | import { Constants } from '../utils/constants'; 11 | import { PieceIconInputManager } from '../utils/inputs/piece-icon-input-manager'; 12 | import { ColorInput, PieceTypeInput } from '../utils/inputs/piece-type-input'; 13 | import { BoardLoader } from './board-state-provider/board-loader/board-loader'; 14 | import { CoordsProvider } from './coords/coords-provider'; 15 | import { DragEndStrategy } from './drag/end/drag-end-strategy'; 16 | import { DragStartStrategy } from './drag/start/drag-start-strategy'; 17 | import { ColorStrategy } from './drawing-tools/colors/color-strategy'; 18 | import { DrawProvider } from './drawing-tools/draw-provider'; 19 | import { DefaultPgnProcessor } from './pgn/default-pgn-processor'; 20 | import { AbstractPgnProcessor } from './pgn/abstract-pgn-processor'; 21 | 22 | export abstract class AbstractEngineFacade { 23 | 24 | public dragStartStrategy: DragStartStrategy = new DragStartStrategy(); 25 | public dragEndStrategy: DragEndStrategy = new DragEndStrategy(); 26 | public pgnProcessor: AbstractPgnProcessor = new DefaultPgnProcessor(); 27 | protected colorStrategy: ColorStrategy = new ColorStrategy(); 28 | 29 | public coords: CoordsProvider = new CoordsProvider(); 30 | public heightAndWidth: number = Constants.DEFAULT_SIZE; 31 | 32 | public freeMode = false; 33 | public dragDisabled: boolean; 34 | public drawDisabled: boolean; 35 | public lightDisabled: boolean; 36 | public darkDisabled: boolean; 37 | public board: Board; 38 | public modal: PiecePromotionModalComponent; 39 | public boardLoader: BoardLoader; 40 | public drawProvider: DrawProvider = new DrawProvider(); 41 | public pieceIconManager: PieceIconInputManager = new PieceIconInputManager(); 42 | public moveHistoryProvider: HistoryMoveProvider = new HistoryMoveProvider(); 43 | public moveDone: boolean; 44 | public disabling = false; 45 | 46 | protected constructor(board: Board) { 47 | this.board = board; 48 | } 49 | 50 | public abstract reset(): void; 51 | 52 | public abstract undo(): void; 53 | 54 | public abstract move(coords: string): void; 55 | 56 | public abstract addPiece( 57 | pieceTypeInput: PieceTypeInput, 58 | colorInput: ColorInput, 59 | coords: string 60 | ): void; 61 | 62 | public abstract onMouseUp( 63 | event: MouseEvent, 64 | pointClicked: Point, 65 | left: number, 66 | top: number 67 | ): void; 68 | 69 | public abstract onMouseDown( 70 | event: MouseEvent, 71 | pointClicked: Point, 72 | left?: number, 73 | top?: number 74 | ): void; 75 | 76 | public abstract onContextMenu( 77 | event: MouseEvent, 78 | ): void; 79 | 80 | public checkIfPawnFirstMove(piece: Piece) { 81 | if (piece instanceof Pawn) { 82 | piece.isMovedAlready = true; 83 | } 84 | } 85 | 86 | public checkIfRookMoved(piece: Piece) { 87 | if (piece instanceof Rook) { 88 | piece.isMovedAlready = true; 89 | } 90 | } 91 | 92 | public checkIfKingMoved(piece: Piece) { 93 | if (piece instanceof King) { 94 | piece.isMovedAlready = true; 95 | } 96 | } 97 | 98 | public getMoveHistory(): HistoryMove[] { 99 | return this.moveHistoryProvider.getAll(); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/board-state-provider/board-loader/default-pieces-loader.ts: -------------------------------------------------------------------------------- 1 | import { Board } from '../../../models/board'; 2 | import { Bishop } from '../../../models/pieces/bishop'; 3 | import { Color } from '../../../models/pieces/color'; 4 | import { King } from '../../../models/pieces/king'; 5 | import { Knight } from '../../../models/pieces/knight'; 6 | import { Pawn } from '../../../models/pieces/pawn'; 7 | import { Point } from '../../../models/pieces/point'; 8 | import { Queen } from '../../../models/pieces/queen'; 9 | import { Rook } from '../../../models/pieces/rook'; 10 | import { UnicodeConstants } from '../../../utils/unicode-constants'; 11 | 12 | export class DefaultPiecesLoader { 13 | 14 | static loadDefaultPieces(board: Board) { 15 | board.pieces = []; 16 | // piony czarne 17 | for (let i = 0; i < 8; ++i) { 18 | board.pieces.push(new Pawn( 19 | new Point(1, i), 20 | Color.BLACK, 21 | UnicodeConstants.BLACK_PAWN, 22 | board 23 | )); 24 | } 25 | board.pieces.push(new Rook( 26 | new Point(0, 0), 27 | Color.BLACK, 28 | UnicodeConstants.BLACK_ROOK, 29 | board 30 | )); 31 | board.pieces.push(new Knight( 32 | new Point(0, 1), 33 | Color.BLACK, 34 | UnicodeConstants.BLACK_KNIGHT, 35 | board 36 | )); 37 | board.pieces.push(new Bishop( 38 | new Point(0, 2), 39 | Color.BLACK, 40 | UnicodeConstants.BLACK_BISHOP, 41 | board 42 | )); 43 | board.pieces.push(new Queen( 44 | new Point(0, 3), 45 | Color.BLACK, 46 | UnicodeConstants.BLACK_QUEEN, 47 | board 48 | )); 49 | board.pieces.push(new King( 50 | new Point(0, 4), 51 | Color.BLACK, 52 | UnicodeConstants.BLACK_KING, 53 | board 54 | )); 55 | board.pieces.push(new Bishop( 56 | new Point(0, 5), 57 | Color.BLACK, 58 | UnicodeConstants.BLACK_BISHOP, 59 | board 60 | )); 61 | board.pieces.push(new Knight( 62 | new Point(0, 6), 63 | Color.BLACK, 64 | UnicodeConstants.BLACK_KNIGHT, 65 | board 66 | )); 67 | board.pieces.push(new Rook( 68 | new Point(0, 7), 69 | Color.BLACK, 70 | UnicodeConstants.BLACK_ROOK, 71 | board 72 | )); 73 | 74 | // piony biale 75 | for (let i = 0; i < 8; ++i) { 76 | board.pieces.push(new Pawn( 77 | new Point(6, i), 78 | Color.WHITE, 79 | UnicodeConstants.WHITE_PAWN, 80 | board 81 | )); 82 | } 83 | board.pieces.push(new Rook( 84 | new Point(7, 0), 85 | Color.WHITE, 86 | UnicodeConstants.WHITE_ROOK, 87 | board 88 | )); 89 | board.pieces.push(new Knight( 90 | new Point(7, 1), 91 | Color.WHITE, 92 | UnicodeConstants.WHITE_KNIGHT, 93 | board 94 | )); 95 | board.pieces.push(new Bishop( 96 | new Point(7, 2), 97 | Color.WHITE, 98 | UnicodeConstants.WHITE_BISHOP, 99 | board 100 | )); 101 | board.pieces.push(new Queen( 102 | new Point(7, 3), 103 | Color.WHITE, 104 | UnicodeConstants.WHITE_QUEEN, 105 | board 106 | )); 107 | board.pieces.push(new King( 108 | new Point(7, 4), 109 | Color.WHITE, 110 | UnicodeConstants.WHITE_KING, 111 | board 112 | )); 113 | board.pieces.push(new Bishop( 114 | new Point(7, 5), 115 | Color.WHITE, 116 | UnicodeConstants.WHITE_BISHOP, 117 | board 118 | )); 119 | board.pieces.push(new Knight( 120 | new Point(7, 6), 121 | Color.WHITE, 122 | UnicodeConstants.WHITE_KNIGHT, 123 | board 124 | )); 125 | board.pieces.push(new Rook( 126 | new Point(7, 7), 127 | Color.WHITE, 128 | UnicodeConstants.WHITE_ROOK, 129 | board 130 | )); 131 | 132 | board.calculateFEN(); 133 | 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/models/pieces/pawn.ts: -------------------------------------------------------------------------------- 1 | import { PieceConstant } from '../../utils/unicode-constants'; 2 | import { Board } from '../board'; 3 | import { Color } from './color'; 4 | import { Piece } from './piece'; 5 | import { Point } from './point'; 6 | 7 | export class Pawn extends Piece { 8 | isMovedAlready = false; 9 | 10 | constructor( 11 | point: Point, 12 | color: Color, 13 | constant: PieceConstant, 14 | board: Board 15 | ) { 16 | super(point, color, constant, 1, board); 17 | } 18 | 19 | getPossibleMoves(): Point[] { 20 | const possiblePoints = []; 21 | const row = this.point.row; 22 | const col = this.point.col; 23 | if ( 24 | (!this.board.reverted && this.color === Color.WHITE) || 25 | (this.board.reverted && this.color === Color.BLACK) 26 | ) { 27 | if (this.board.isFieldEmpty(row - 1, col)) { 28 | possiblePoints.push(new Point(row - 1, col)); 29 | 30 | if ( 31 | !this.isMovedAlready && 32 | this.board.isFieldEmpty(row - 2, col) 33 | ) { 34 | possiblePoints.push(new Point(row - 2, col)); 35 | } 36 | } 37 | } else { 38 | if ( 39 | /*!board.isFieldTakenByEnemy(row + 1, col, Color.WHITE) &&*/ this.board.isFieldEmpty( 40 | row + 1, 41 | col 42 | ) 43 | ) { 44 | possiblePoints.push(new Point(row + 1, col)); 45 | 46 | if ( 47 | !this.isMovedAlready && 48 | this.board.isFieldEmpty(row + 2, col) 49 | ) { 50 | possiblePoints.push(new Point(row + 2, col)); 51 | } 52 | } 53 | } 54 | return possiblePoints; 55 | } 56 | 57 | getPossibleCaptures(): Point[] { 58 | const possiblePoints = []; 59 | const row = this.point.row; 60 | const col = this.point.col; 61 | 62 | if ( 63 | (!this.board.reverted && this.color === Color.WHITE) || 64 | (this.board.reverted && this.color === Color.BLACK) 65 | ) { 66 | if ( 67 | this.board.isFieldTakenByEnemy( 68 | row - 1, 69 | col - 1, 70 | this.color === Color.WHITE ? Color.BLACK : Color.WHITE 71 | ) 72 | ) { 73 | possiblePoints.push(new Point(row - 1, col - 1)); 74 | } 75 | if ( 76 | this.board.isFieldTakenByEnemy( 77 | row - 1, 78 | col + 1, 79 | this.color === Color.WHITE ? Color.BLACK : Color.WHITE 80 | ) 81 | ) { 82 | possiblePoints.push(new Point(row - 1, col + 1)); 83 | } 84 | } else { 85 | if ( 86 | this.board.isFieldTakenByEnemy( 87 | row + 1, 88 | col - 1, 89 | this.color === Color.WHITE ? Color.BLACK : Color.WHITE 90 | ) 91 | ) { 92 | possiblePoints.push(new Point(row + 1, col - 1)); 93 | } 94 | if ( 95 | this.board.isFieldTakenByEnemy( 96 | row + 1, 97 | col + 1, 98 | this.color === Color.WHITE ? Color.BLACK : Color.WHITE 99 | ) 100 | ) { 101 | possiblePoints.push(new Point(row + 1, col + 1)); 102 | } 103 | } 104 | 105 | if ( 106 | this.board.enPassantPoint && 107 | this.board.enPassantPiece.color === 108 | (this.color === Color.WHITE ? Color.BLACK : Color.WHITE) 109 | ) { 110 | if ( 111 | row === this.board.enPassantPiece.point.row && 112 | Math.abs(this.board.enPassantPiece.point.col - col) === 1 113 | ) { 114 | possiblePoints.push(this.board.enPassantPoint); 115 | } 116 | } 117 | 118 | return possiblePoints; 119 | } 120 | 121 | getCoveredFields(): Point[] { 122 | const possiblePoints = []; 123 | const row = this.point.row; 124 | const col = this.point.col; 125 | 126 | if ( 127 | (!this.board.reverted && this.color === Color.WHITE) || 128 | (this.board.reverted && this.color === Color.BLACK) 129 | ) { 130 | possiblePoints.push(new Point(row - 1, col - 1)); 131 | 132 | possiblePoints.push(new Point(row - 1, col + 1)); 133 | } else { 134 | possiblePoints.push(new Point(row + 1, col - 1)); 135 | 136 | possiblePoints.push(new Point(row + 1, col + 1)); 137 | } 138 | 139 | return possiblePoints; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewChild } from '@angular/core'; 2 | import { 3 | MoveChange, 4 | NgxChessBoardComponent, 5 | PieceIconInput 6 | } from 'ngx-chess-board'; 7 | import { 8 | ColorInput, 9 | PieceTypeInput 10 | } from 'ngx-chess-board'; 11 | import { FenComponent } from './components/fen/fen.component'; 12 | 13 | @Component({ 14 | selector: 'app-root', 15 | templateUrl: './app.component.html', 16 | styleUrls: ['./app.component.scss'], 17 | }) 18 | export class AppComponent { 19 | @ViewChild('board') 20 | boardManager: NgxChessBoardComponent; 21 | 22 | @ViewChild('fenManager') fenManager: FenComponent; 23 | public fen = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'; 24 | private currentStateIndex: number; 25 | manualMove = 'd2d4'; 26 | icons: PieceIconInput = { 27 | blackBishopUrl: '', 28 | blackKingUrl: '', 29 | blackKnightUrl: '', 30 | blackPawnUrl: '', 31 | blackQueenUrl: '', 32 | blackRookUrl: '', 33 | whiteBishopUrl: '', 34 | whiteKingUrl: '', 35 | whiteKnightUrl: '', 36 | whitePawnUrl: '', 37 | whiteQueenUrl: '', 38 | whiteRookUrl: '' 39 | }; 40 | 41 | public darkTileColor = 'rgb(97, 84, 61)'; 42 | public lightTileColor = '#BAA378'; 43 | public size = 400; 44 | public dragDisabled = false; 45 | public drawDisabled = false; 46 | public lightDisabled = false; 47 | public darkDisabled = false; 48 | public freeMode = false; 49 | public addPieceCoords: string = 'a4'; 50 | public selectedPiece = '1'; 51 | public selectedColor = '1'; 52 | public pgn: string = ''; 53 | 54 | public reset(): void { 55 | alert('Resetting board'); 56 | this.boardManager.reset(); 57 | this.fen = this.boardManager.getFEN(); 58 | this.freeMode = false; 59 | } 60 | 61 | public reverse(): void { 62 | this.boardManager.reverse(); 63 | } 64 | 65 | public undo(): void { 66 | this.boardManager.undo(); 67 | this.fen = this.boardManager.getFEN(); 68 | this.pgn = this.boardManager.getPGN(); 69 | } 70 | 71 | public setFen(): void { 72 | if (this.fen) { 73 | this.boardManager.setFEN(this.fen); 74 | } 75 | } 76 | 77 | public moveCallback(move: MoveChange): void { 78 | this.fen = this.boardManager.getFEN(); 79 | this.pgn = this.boardManager.getPGN(); 80 | console.log(move); 81 | } 82 | 83 | public moveManual(): void { 84 | this.boardManager.move(this.manualMove); 85 | } 86 | 87 | getFEN() { 88 | let fen = this.boardManager.getFEN(); 89 | alert(fen); 90 | } 91 | 92 | showMoveHistory() { 93 | alert(JSON.stringify(this.boardManager.getMoveHistory())); 94 | } 95 | 96 | switchDrag() { 97 | this.dragDisabled = !this.dragDisabled; 98 | } 99 | 100 | switchDraw() { 101 | this.drawDisabled = !this.drawDisabled; 102 | } 103 | 104 | switchDarkDisabled() { 105 | this.darkDisabled = !this.darkDisabled; 106 | } 107 | 108 | switchLightDisabled() { 109 | this.lightDisabled = !this.lightDisabled; 110 | } 111 | 112 | switchFreeMode() { 113 | this.freeMode = !this.freeMode; 114 | } 115 | 116 | addPiece() { 117 | let piece = this.resolveSelectedPiece(); 118 | let color = this.resolveSelectedColor(); 119 | this.boardManager.addPiece(piece, color, this.addPieceCoords); 120 | } 121 | 122 | private resolveSelectedPiece(): PieceTypeInput { 123 | switch (this.selectedPiece) { 124 | case '1': 125 | return PieceTypeInput.QUEEN; 126 | case '2': 127 | return PieceTypeInput.KING; 128 | case '3': 129 | return PieceTypeInput.ROOK; 130 | case '4': 131 | return PieceTypeInput.BISHOP; 132 | case '5': 133 | return PieceTypeInput.KNIGHT; 134 | case '6': 135 | return PieceTypeInput.PAWN; 136 | } 137 | } 138 | 139 | private resolveSelectedColor(): ColorInput { 140 | switch (this.selectedColor) { 141 | case '1': 142 | return ColorInput.LIGHT; 143 | case '2': 144 | return ColorInput.DARK; 145 | } 146 | } 147 | 148 | public setPgn() { 149 | this.boardManager.setPGN(this.pgn); 150 | } 151 | 152 | loadDefaultPgn() { 153 | this.pgn = '1. c4 b5 2. cxb5 c6 3. bxc6 Nxc6 4. Qa4 a6\n' + 154 | '5. Qxa6 Rb8 6. b3 d5 7. f4 e5 8. fxe5 f6\n' + 155 | '9. exf6 gxf6 10. Nf3 f5 11. Ne5 Bb7 12. Qxb7 Na7\n' + 156 | '13. Qxb8 Qxb8 14. Kf2 Kd8 15. Nc3 Be7 16. Nc4 Bf6\n' + 157 | '17. Nb6 Nb5 18. Nbxd5 f4 19. Ne4 Na7 20. Nexf6'; 158 | this.setPgn(); 159 | } 160 | 161 | getPGN() { 162 | alert(this.boardManager.getPGN()); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "chess-board": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:component": { 10 | "style": "scss" 11 | } 12 | }, 13 | "root": "", 14 | "sourceRoot": "src", 15 | "prefix": "app", 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "outputPath": "dist/chess-board", 21 | "index": "src/index.html", 22 | "main": "src/main.ts", 23 | "polyfills": "src/polyfills.ts", 24 | "tsConfig": "tsconfig.app.json", 25 | "assets": [ 26 | "src/favicon.ico", 27 | "src/assets" 28 | ], 29 | "styles": [ 30 | "node_modules/bootstrap/dist/css/bootstrap.min.css", 31 | "src/styles.scss" 32 | ], 33 | "scripts": [], 34 | "vendorChunk": true, 35 | "extractLicenses": false, 36 | "buildOptimizer": false, 37 | "sourceMap": true, 38 | "optimization": false, 39 | "namedChunks": true 40 | }, 41 | "configurations": { 42 | "production": { 43 | "fileReplacements": [ 44 | { 45 | "replace": "src/environments/environment.ts", 46 | "with": "src/environments/environment.prod.ts" 47 | } 48 | ], 49 | "optimization": true, 50 | "outputHashing": "all", 51 | "sourceMap": false, 52 | "namedChunks": false, 53 | "extractLicenses": true, 54 | "vendorChunk": false, 55 | "buildOptimizer": true, 56 | "budgets": [ 57 | { 58 | "type": "initial", 59 | "maximumWarning": "2mb", 60 | "maximumError": "5mb" 61 | }, 62 | { 63 | "type": "anyComponentStyle", 64 | "maximumWarning": "6kb", 65 | "maximumError": "10kb" 66 | } 67 | ] 68 | } 69 | }, 70 | "defaultConfiguration": "" 71 | }, 72 | "serve": { 73 | "builder": "@angular-devkit/build-angular:dev-server", 74 | "options": { 75 | "buildTarget": "chess-board:build" 76 | }, 77 | "configurations": { 78 | "production": { 79 | "buildTarget": "chess-board:build:production" 80 | } 81 | } 82 | }, 83 | "extract-i18n": { 84 | "builder": "@angular-devkit/build-angular:extract-i18n", 85 | "options": { 86 | "buildTarget": "chess-board:build" 87 | } 88 | }, 89 | "test": { 90 | "builder": "@angular-devkit/build-angular:karma", 91 | "options": { 92 | "main": "src/test.ts", 93 | "polyfills": "src/polyfills.ts", 94 | "tsConfig": "tsconfig.spec.json", 95 | "karmaConfig": "karma.conf.js", 96 | "assets": [ 97 | "src/favicon.ico", 98 | "src/assets" 99 | ], 100 | "styles": [ 101 | "src/styles.scss" 102 | ], 103 | "scripts": [] 104 | } 105 | }, 106 | "e2e": { 107 | "builder": "@angular-devkit/build-angular:protractor", 108 | "options": { 109 | "protractorConfig": "e2e/protractor.conf.js", 110 | "devServerTarget": "chess-board:serve" 111 | }, 112 | "configurations": { 113 | "production": { 114 | "devServerTarget": "chess-board:serve:production" 115 | } 116 | } 117 | } 118 | } 119 | }, 120 | "ngx-chess-board": { 121 | "projectType": "library", 122 | "root": "projects/ngx-chess-board", 123 | "sourceRoot": "projects/ngx-chess-board/src", 124 | "prefix": "lib", 125 | "architect": { 126 | "build": { 127 | "builder": "@angular-devkit/build-angular:ng-packagr", 128 | "options": { 129 | "tsConfig": "projects/ngx-chess-board/tsconfig.lib.json", 130 | "project": "projects/ngx-chess-board/ng-package.json" 131 | } 132 | , "configurations": { 133 | "production": { 134 | "tsConfig": "projects/ngx-chess-board/tsconfig.lib.prod.json" 135 | } 136 | } 137 | }, 138 | "test": { 139 | "builder": "@angular-devkit/build-angular:karma", 140 | "options": { 141 | "main": "projects/ngx-chess-board/src/test.ts", 142 | "tsConfig": "projects/ngx-chess-board/tsconfig.spec.json", 143 | "karmaConfig": "projects/ngx-chess-board/karma.conf.js" 144 | } 145 | } 146 | } 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ngx-chess-board 2 | npm CircleCI 3 | 4 | ngx-chess-board is a project which allows to add chess game for two players on your site. 5 | 6 | ## Instalation 7 | ```bash 8 | npm i ngx-chess-board 9 | ``` 10 | 11 | ## Demo 12 | See [demo version](https://grzegorz103.github.io/ngx-chess-board/chess-board/index.html) 13 | 14 | ## Screenshots 15 | ![alt scr](https://i.ibb.co/NsRx0Pj/image.png) 16 | ![alt scr](https://i.ibb.co/b6ryJmP/image.png) 17 | 18 | ## Use example 19 | 20 | `ngx-chess-board` exports following components: 21 | * `NgxChessBoardModule` 22 | * `NgxChessBoardService` 23 | * `NgxChessBoardView` 24 | 25 | To start, you have to import the `NgxChessBoardModule`: 26 | ```typescript 27 | import { NgxChessBoardModule } from "ngx-chess-board"; 28 | 29 | ... 30 | @NgModule({ 31 | ... 32 | imports: [ 33 | ... 34 | NgxChessBoardModule 35 | ... 36 | ], 37 | ... 38 | }) 39 | export class AppModule { } 40 | ``` 41 | Next, add following tag in your component's view to render the chess board: 42 | ```html 43 | 44 | ``` 45 | 46 | Then you can inject NgxChessBoardService into your component: 47 | 48 | ```typescript 49 | import {NgxChessBoardService} from 'ngx-chess-board'; 50 | 51 | ... 52 | 53 | constructor(private ngxChessBoardService: NgxChessBoardService) { } 54 | ``` 55 | 56 | You can add reference to `NgxChessBoardView` to interact with specified chess board: 57 | HTML file: 58 | ```html 59 | 60 | ``` 61 | .ts file: 62 | ```typescript 63 | import {NgxChessBoardView} from 'ngx-chess-board'; 64 | 65 | ... 66 | 67 | @ViewChild('board', {static: false}) board: NgxChessBoardView; 68 | 69 | ... 70 | 71 | reset() { 72 | this.board.reset(); 73 | } 74 | 75 | ... 76 | ``` 77 | 78 | 79 | ## API 80 | 81 | #### @Inputs() 82 | 83 | Input | Type | Description 84 | | :---: | :---: | :---: | 85 | `[size]` | number | Sets size of the chess board (in pixels). Default value is 400. Size must be in range between 100-4000. 86 | `[lightTileColor]` | string | Sets color of light tiles. Accepts predefined color names, RGB, HEX, HSL. 87 | `[darkTileColor]` | string | Sets color of dark tiles. Accepts predefined color names, RGB, HEX, HSL. 88 | `[showCoords]` | boolean | Sets visibility of coordination bar. Default value is true 89 | `[sourcePointColor]` | string | Sets background color for the source box from where the piece is moved. 90 | `[destinationPointColor]` | string | Sets background color for the destination box where the piece is dropped. 91 | `[showLastMove]` | boolean | Specifies whether the last move should be highlighted or not. 92 | `[showLegalMoves]` | boolean | Specifies whether the legal moves should be highlighted or not. 93 | `[dragDisabled]` | boolean | Specifies whether piece dragging is disabled. Default value is false 94 | `[drawDisabled]` | boolean| Specifies whether drawing with right mouse button is disabled. Default value is false 95 | `[lightDisabled]` | boolean | Specifies whether light pieces are disabled to move. Default value is false 96 | `[darkDisabled]` | boolean | Specifies whether dark pieces are disabled to move. Default value is false 97 | `[pieceIcons]` | PieceIconInput | Sets custom piece icons. Accepts SVG, IMG. Default the ASCII icons are used. 98 | `[freeMode]` | boolean | Sets the board in free mode. In this mode pieces can be moved freely, even to tiles that aren't in possible moves or possible captures. 99 | `[showActivePiece]` | boolean | Specifies whether active piece clicked has to be highlighted. Default value is true 100 | `[showPossibleCaptures]` | boolean | Specifies whether possible captures have to be highlighted. Default value is true 101 |
102 | 103 | #### Outputs 104 | 105 | Name | Type | Description 106 | | :---: | :---: | :---: | 107 | `(moveChange)` | EventEmitter\ | Dispatch event when piece has moved 108 |
109 | 110 | #### NgxChessBoardView methods 111 | 112 | Method | Return type | Description 113 | | :---: | :---: | :---: | 114 | reset() | void | Resets specified chess game 115 | reverse() | void | Reverse specified chess board 116 | undo() | void | Undo the last move 117 | getMoveHistory() | JSON | Returns array in JSON format which contains information about every move 118 | setFEN(fen: string) | void | Allows to import specified board position by FEN 119 | getFEN() | string | Returns current board FEN position 120 | move(coords: string) | void | Makes move by specified coords. The coords parameter contains source and destination position e.g. 'd2d4'. 121 | addPiece(pieceTypeInput: PieceTypeInput, colorInput: ColorInput, coords: string) | void | Adds new piece to the board at specified square, e.g. 'd4'. Left click on a piece with control removes piece. Free mode must be enabled first. 122 | setPGN(coords:string) | void | Imports board position in PGN notation 123 | getPGN() | string | Returns current board position in PGN notation 124 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/README.md: -------------------------------------------------------------------------------- 1 | # ngx-chess-board 2 | npm CircleCI 3 | 4 | ngx-chess-board is a project which allows to add chess game for two players on your site. 5 | 6 | ## Instalation 7 | ```bash 8 | npm i ngx-chess-board 9 | ``` 10 | 11 | ## Demo 12 | See [demo version](https://grzegorz103.github.io/ngx-chess-board/chess-board/index.html) 13 | 14 | ## Screenshots 15 | ![alt scr](https://i.ibb.co/NsRx0Pj/image.png) 16 | ![alt scr](https://i.ibb.co/b6ryJmP/image.png) 17 | 18 | ## Use example 19 | 20 | `ngx-chess-board` exports following components: 21 | * `NgxChessBoardModule` 22 | * `NgxChessBoardService` 23 | * `NgxChessBoardView` 24 | 25 | To start, you have to import the `NgxChessBoardModule`: 26 | ```typescript 27 | import { NgxChessBoardModule } from "ngx-chess-board"; 28 | 29 | ... 30 | @NgModule({ 31 | ... 32 | imports: [ 33 | ... 34 | NgxChessBoardModule 35 | ... 36 | ], 37 | ... 38 | }) 39 | export class AppModule { } 40 | ``` 41 | Next, add following tag in your component's view to render the chess board: 42 | ```html 43 | 44 | ``` 45 | 46 | Then you can inject NgxChessBoardService into your component: 47 | 48 | ```typescript 49 | import {NgxChessBoardService} from 'ngx-chess-board'; 50 | 51 | ... 52 | 53 | constructor(private ngxChessBoardService: NgxChessBoardService) { } 54 | ``` 55 | 56 | You can add reference to `NgxChessBoardView` to interact with specified chess board: 57 | HTML file: 58 | ```html 59 | 60 | ``` 61 | .ts file: 62 | ```typescript 63 | import {NgxChessBoardView} from 'ngx-chess-board'; 64 | 65 | ... 66 | 67 | @ViewChild('board', {static: false}) board: NgxChessBoardView; 68 | 69 | ... 70 | 71 | reset() { 72 | this.board.reset(); 73 | } 74 | 75 | ... 76 | ``` 77 | 78 | 79 | ## API 80 | 81 | #### @Inputs() 82 | 83 | Input | Type | Description 84 | | :---: | :---: | :---: | 85 | `[size]` | number | Sets size of the chess board (in pixels). Default value is 400. Size must be in range between 100-4000. 86 | `[lightTileColor]` | string | Sets color of light tiles. Accepts predefined color names, RGB, HEX, HSL. 87 | `[darkTileColor]` | string | Sets color of dark tiles. Accepts predefined color names, RGB, HEX, HSL. 88 | `[showCoords]` | boolean | Sets visibility of coordination bar. Default value is true 89 | `[sourcePointColor]` | string | Sets background color for the source box from where the piece is moved. 90 | `[destinationPointColor]` | string | Sets background color for the destination box where the piece is dropped. 91 | `[showLastMove]` | boolean | Specifies whether the last move should be highlighted or not. 92 | `[showLegalMoves]` | boolean | Specifies whether the legal moves should be highlighted or not. 93 | `[dragDisabled]` | boolean | Specifies whether piece dragging is disabled. Default value is false 94 | `[drawDisabled]` | boolean| Specifies whether drawing with right mouse button is disabled. Default value is false 95 | `[lightDisabled]` | boolean | Specifies whether light pieces are disabled to move. Default value is false 96 | `[darkDisabled]` | boolean | Specifies whether dark pieces are disabled to move. Default value is false 97 | `[pieceIcons]` | PieceIconInput | Sets custom piece icons. Accepts SVG, IMG. Default the ASCII icons are used. 98 | `[freeMode]` | boolean | Sets the board in free mode. In this mode pieces can be moved freely, even to tiles that aren't in possible moves or possible captures. 99 | `[showActivePiece]` | boolean | Specifies whether active piece clicked has to be highlighted. Default value is true 100 | `[showPossibleCaptures]` | boolean | Specifies whether possible captures have to be highlighted. Default value is true 101 |
102 | 103 | #### Outputs 104 | 105 | Name | Type | Description 106 | | :---: | :---: | :---: | 107 | `(moveChange)` | EventEmitter\ | Dispatch event when piece has moved 108 |
109 | 110 | #### NgxChessBoardView methods 111 | 112 | Method | Return type | Description 113 | | :---: | :---: | :---: | 114 | reset() | void | Resets specified chess game 115 | reverse() | void | Reverse specified chess board 116 | undo() | void | Undo the last move 117 | getMoveHistory() | JSON | Returns array in JSON format which contains information about every move 118 | setFEN(fen: string) | void | Allows to import specified board position by FEN 119 | getFEN() | string | Returns current board FEN position 120 | move(coords: string) | void | Makes move by specified coords. The coords parameter contains source and destination position e.g. 'd2d4'. 121 | addPiece(pieceTypeInput: PieceTypeInput, colorInput: ColorInput, coords: string) | void | Adds new piece to the board at specified square, e.g. 'd4'. Left click on a piece with control removes piece. Free mode must be enabled first. 122 | setPGN(coords:string) | void | Imports board position in PGN notation 123 | getPGN() | string | Returns current board position in PGN notation 124 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/ngx-chess-board.component.html: -------------------------------------------------------------------------------- 1 |
10 |
11 |
27 |
28 |
32 |
42 | 48 | {{engineFacade.coords.yCoords[i]}} 49 | 50 | 56 | {{engineFacade.coords.xCoords[j]}} 57 | 58 |
62 |
67 |
68 |
69 |
70 |
71 |
72 | 77 | 78 | 86 | 90 | 91 | 92 | 102 | 111 | 112 | 115 |
116 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/models/pieces/knight.ts: -------------------------------------------------------------------------------- 1 | import { PieceConstant } from '../../utils/unicode-constants'; 2 | import { Board } from '../board'; 3 | import { Color } from './color'; 4 | import { Piece } from './piece'; 5 | import { Point } from './point'; 6 | 7 | export class Knight extends Piece { 8 | isMovedAlready = false; 9 | 10 | constructor( 11 | point: Point, 12 | color: Color, 13 | constant: PieceConstant, 14 | board: Board 15 | ) { 16 | super(point, color, constant, 3, board); 17 | } 18 | 19 | getPossibleMoves(): Point[] { 20 | const possiblePoints = []; 21 | 22 | const row = this.point.row; 23 | const col = this.point.col; 24 | 25 | // gora -> lewo 26 | if (this.board.isFieldEmpty(row - 2, col - 1)) { 27 | possiblePoints.push(new Point(row - 2, col - 1)); 28 | } 29 | 30 | // gora -> prawo 31 | if (this.board.isFieldEmpty(row - 2, col + 1)) { 32 | possiblePoints.push(new Point(row - 2, col + 1)); 33 | } 34 | 35 | // lewo -> gora 36 | if (this.board.isFieldEmpty(row - 1, col - 2)) { 37 | possiblePoints.push(new Point(row - 1, col - 2)); 38 | } 39 | 40 | // prawo -> gora 41 | if (this.board.isFieldEmpty(row - 1, col + 2)) { 42 | possiblePoints.push(new Point(row - 1, col + 2)); 43 | } 44 | 45 | // lewo -> dol 46 | if (this.board.isFieldEmpty(row + 1, col - 2)) { 47 | possiblePoints.push(new Point(row + 1, col - 2)); 48 | } 49 | 50 | // prawo -> dol 51 | if (this.board.isFieldEmpty(row + 1, col + 2)) { 52 | possiblePoints.push(new Point(row + 1, col + 2)); 53 | } 54 | 55 | // dol -> lewo 56 | if (this.board.isFieldEmpty(row + 2, col - 1)) { 57 | possiblePoints.push(new Point(row + 2, col - 1)); 58 | } 59 | 60 | // dol -> prawo 61 | if (this.board.isFieldEmpty(row + 2, col + 1)) { 62 | possiblePoints.push(new Point(row + 2, col + 1)); 63 | } 64 | 65 | return possiblePoints; 66 | } 67 | 68 | getPossibleCaptures(): Point[] { 69 | const possiblePoints = []; 70 | 71 | const row = this.point.row; 72 | const col = this.point.col; 73 | 74 | // gora -> lewo 75 | if ( 76 | this.board.isFieldTakenByEnemy( 77 | row - 2, 78 | col - 1, 79 | this.color === Color.WHITE ? Color.BLACK : Color.WHITE 80 | ) 81 | ) { 82 | possiblePoints.push(new Point(row - 2, col - 1)); 83 | } 84 | 85 | // gora -> prawo 86 | if ( 87 | this.board.isFieldTakenByEnemy( 88 | row - 2, 89 | col + 1, 90 | this.color === Color.WHITE ? Color.BLACK : Color.WHITE 91 | ) 92 | ) { 93 | possiblePoints.push(new Point(row - 2, col + 1)); 94 | } 95 | 96 | // lewo -> gora 97 | if ( 98 | this.board.isFieldTakenByEnemy( 99 | row - 1, 100 | col - 2, 101 | this.color === Color.WHITE ? Color.BLACK : Color.WHITE 102 | ) 103 | ) { 104 | possiblePoints.push(new Point(row - 1, col - 2)); 105 | } 106 | 107 | // prawo -> gora 108 | if ( 109 | this.board.isFieldTakenByEnemy( 110 | row - 1, 111 | col + 2, 112 | this.color === Color.WHITE ? Color.BLACK : Color.WHITE 113 | ) 114 | ) { 115 | possiblePoints.push(new Point(row - 1, col + 2)); 116 | } 117 | 118 | // lewo -> dol 119 | if ( 120 | this.board.isFieldTakenByEnemy( 121 | row + 1, 122 | col - 2, 123 | this.color === Color.WHITE ? Color.BLACK : Color.WHITE 124 | ) 125 | ) { 126 | possiblePoints.push(new Point(row + 1, col - 2)); 127 | } 128 | 129 | // prawo -> dol 130 | if ( 131 | this.board.isFieldTakenByEnemy( 132 | row + 1, 133 | col + 2, 134 | this.color === Color.WHITE ? Color.BLACK : Color.WHITE 135 | ) 136 | ) { 137 | possiblePoints.push(new Point(row + 1, col + 2)); 138 | } 139 | 140 | // dol -> lewo 141 | if ( 142 | this.board.isFieldTakenByEnemy( 143 | row + 2, 144 | col - 1, 145 | this.color === Color.WHITE ? Color.BLACK : Color.WHITE 146 | ) 147 | ) { 148 | possiblePoints.push(new Point(row + 2, col - 1)); 149 | } 150 | 151 | // dol -> prawo 152 | if ( 153 | this.board.isFieldTakenByEnemy( 154 | row + 2, 155 | col + 1, 156 | this.color === Color.WHITE ? Color.BLACK : Color.WHITE 157 | ) 158 | ) { 159 | possiblePoints.push(new Point(row + 2, col + 1)); 160 | } 161 | 162 | return possiblePoints; 163 | } 164 | 165 | getCoveredFields(): Point[] { 166 | const possiblePoints = []; 167 | 168 | const row = this.point.row; 169 | const col = this.point.col; 170 | 171 | // gora -> lewo 172 | possiblePoints.push(new Point(row - 2, col - 1)); 173 | 174 | // gora -> prawo 175 | possiblePoints.push(new Point(row - 2, col + 1)); 176 | 177 | // lewo -> gora 178 | possiblePoints.push(new Point(row - 1, col - 2)); 179 | 180 | // prawo -> gora 181 | possiblePoints.push(new Point(row - 1, col + 2)); 182 | 183 | // lewo -> dol 184 | possiblePoints.push(new Point(row + 1, col - 2)); 185 | 186 | // prawo -> dol 187 | possiblePoints.push(new Point(row + 1, col + 2)); 188 | 189 | // dol -> lewo 190 | possiblePoints.push(new Point(row + 2, col - 1)); 191 | 192 | // dol -> prawo 193 | possiblePoints.push(new Point(row + 2, col + 1)); 194 | 195 | return possiblePoints; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/models/pieces/rook.ts: -------------------------------------------------------------------------------- 1 | import { PieceConstant } from '../../utils/unicode-constants'; 2 | import { Board } from '../board'; 3 | import { Color } from './color'; 4 | import { King } from './king'; 5 | import { Piece } from './piece'; 6 | import { Point } from './point'; 7 | 8 | export class Rook extends Piece { 9 | isMovedAlready = false; 10 | 11 | constructor( 12 | point: Point, 13 | color: Color, 14 | constant: PieceConstant, 15 | board: Board 16 | ) { 17 | super(point, color, constant, 5, board); 18 | } 19 | 20 | getPossibleMoves(): Point[] { 21 | const possiblePoints = []; 22 | 23 | const row = this.point.row; 24 | const col = this.point.col; 25 | 26 | for (let i = row + 1; i < 8; ++i) { 27 | // dol 28 | if (this.board.isFieldEmpty(i, col)) { 29 | possiblePoints.push(new Point(i, col)); 30 | } else { 31 | break; 32 | } 33 | } 34 | 35 | for (let i = row - 1; i >= 0; --i) { 36 | // gora 37 | if (this.board.isFieldEmpty(i, col)) { 38 | possiblePoints.push(new Point(i, col)); 39 | } else { 40 | break; 41 | } 42 | } 43 | 44 | for (let j = col - 1; j >= 0; --j) { 45 | // lewo 46 | if (this.board.isFieldEmpty(row, j)) { 47 | possiblePoints.push(new Point(row, j)); 48 | } else { 49 | break; 50 | } 51 | } 52 | 53 | for (let j = col + 1; j < 8; ++j) { 54 | // prawo 55 | if (this.board.isFieldEmpty(row, j)) { 56 | possiblePoints.push(new Point(row, j)); 57 | } else { 58 | break; 59 | } 60 | } 61 | 62 | return possiblePoints; 63 | } 64 | 65 | getPossibleCaptures(): Point[] { 66 | const possiblePoints = []; 67 | 68 | const row = this.point.row; 69 | const col = this.point.col; 70 | 71 | for (let i = row + 1; i < 8; ++i) { 72 | // dol 73 | if ( 74 | this.board.isFieldTakenByEnemy( 75 | i, 76 | col, 77 | this.color === Color.WHITE ? Color.BLACK : Color.WHITE 78 | ) 79 | ) { 80 | possiblePoints.push(new Point(i, col)); 81 | break; 82 | } else { 83 | if (!this.board.isFieldEmpty(i, col)) { 84 | break; 85 | } 86 | } 87 | } 88 | 89 | for (let i = row - 1; i >= 0; --i) { 90 | // gora 91 | if ( 92 | this.board.isFieldTakenByEnemy( 93 | i, 94 | col, 95 | this.color === Color.WHITE ? Color.BLACK : Color.WHITE 96 | ) 97 | ) { 98 | possiblePoints.push(new Point(i, col)); 99 | break; 100 | } else { 101 | if (!this.board.isFieldEmpty(i, col)) { 102 | break; 103 | } 104 | } 105 | } 106 | 107 | for (let j = col - 1; j >= 0; --j) { 108 | // lewo 109 | if ( 110 | this.board.isFieldTakenByEnemy( 111 | row, 112 | j, 113 | this.color === Color.WHITE ? Color.BLACK : Color.WHITE 114 | ) 115 | ) { 116 | possiblePoints.push(new Point(row, j)); 117 | break; 118 | } else { 119 | if (!this.board.isFieldEmpty(row, j)) { 120 | break; 121 | } 122 | } 123 | } 124 | 125 | for (let j = col + 1; j < 8; ++j) { 126 | // prawo 127 | if ( 128 | this.board.isFieldTakenByEnemy( 129 | row, 130 | j, 131 | this.color === Color.WHITE ? Color.BLACK : Color.WHITE 132 | ) 133 | ) { 134 | possiblePoints.push(new Point(row, j)); 135 | break; 136 | } else { 137 | if (!this.board.isFieldEmpty(row, j)) { 138 | break; 139 | } 140 | } 141 | } 142 | 143 | return possiblePoints; 144 | } 145 | 146 | getCoveredFields(): Point[] { 147 | const possiblePoints = []; 148 | 149 | const row = this.point.row; 150 | const col = this.point.col; 151 | 152 | for (let i = row + 1; i < 8; ++i) { 153 | // dol 154 | if (this.board.isFieldEmpty(i, col)) { 155 | possiblePoints.push(new Point(i, col)); 156 | } else { 157 | if (!(this.board.getPieceByField instanceof King)) { 158 | possiblePoints.push(new Point(i, col)); 159 | break; 160 | } 161 | } 162 | } 163 | 164 | for (let i = row - 1; i >= 0; --i) { 165 | // gora 166 | if (this.board.isFieldEmpty(i, col)) { 167 | possiblePoints.push(new Point(i, col)); 168 | } else { 169 | if (!(this.board.getPieceByField instanceof King)) { 170 | possiblePoints.push(new Point(i, col)); 171 | break; 172 | } 173 | } 174 | } 175 | 176 | for (let j = col - 1; j >= 0; --j) { 177 | // lewo 178 | if (this.board.isFieldEmpty(row, j)) { 179 | possiblePoints.push(new Point(row, j)); 180 | } else { 181 | if (!(this.board.getPieceByField instanceof King)) { 182 | possiblePoints.push(new Point(row, j)); 183 | break; 184 | } 185 | } 186 | } 187 | 188 | for (let j = col + 1; j < 8; ++j) { 189 | // prawo 190 | if (this.board.isFieldEmpty(row, j)) { 191 | possiblePoints.push(new Point(row, j)); 192 | } else { 193 | if (!(this.board.getPieceByField instanceof King)) { 194 | possiblePoints.push(new Point(row, j)); 195 | break; 196 | } 197 | } 198 | } 199 | 200 | return possiblePoints; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/pgn/default-pgn-processor.ts: -------------------------------------------------------------------------------- 1 | import { Board } from '../../models/board'; 2 | import { King } from '../../models/pieces/king'; 3 | import { Pawn } from '../../models/pieces/pawn'; 4 | import { Piece } from '../../models/pieces/piece'; 5 | import { Point } from '../../models/pieces/point'; 6 | import { MoveUtils } from '../../utils/move-utils'; 7 | import { AbstractPgnProcessor } from './abstract-pgn-processor'; 8 | 9 | export class DefaultPgnProcessor extends AbstractPgnProcessor { 10 | 11 | public process( 12 | board: Board, 13 | sourcePiece: Piece, 14 | destPoint: Point, 15 | destPiece?: Piece 16 | ): void { 17 | this.currentIndex += 0.5; 18 | let currentMove = ''; 19 | if(this.currentIndex % Math.floor(this.currentIndex) === 0) { 20 | currentMove = this.currentIndex + '. '; 21 | } 22 | let possibleCaptures = []; 23 | let possibleMoves = []; 24 | 25 | if (destPiece) { 26 | possibleCaptures = MoveUtils.findPieceByPossibleCapturesContaining( 27 | MoveUtils.formatSingle(destPoint, board.reverted), 28 | board, 29 | sourcePiece.color 30 | ).filter(piece => piece.constructor.name === sourcePiece.constructor.name); 31 | } 32 | possibleMoves = MoveUtils.findPieceByPossibleMovesContaining( 33 | MoveUtils.formatSingle(destPoint, board.reverted), 34 | board, 35 | sourcePiece.color 36 | ).filter(piece => piece.constructor.name === sourcePiece.constructor.name); 37 | 38 | if (sourcePiece instanceof Pawn && !destPiece && possibleCaptures.length === 0) { 39 | currentMove += MoveUtils.formatSingle(destPoint, board.reverted); 40 | } else { 41 | if (sourcePiece instanceof Pawn && destPiece) { 42 | currentMove += (MoveUtils.formatSingle( 43 | sourcePiece.point, 44 | board.reverted 45 | ).substring(0, 1) + 'x' + MoveUtils.formatSingle( 46 | destPoint, 47 | board.reverted 48 | )); 49 | } else { 50 | if (sourcePiece instanceof King && (Math.abs(sourcePiece.point.col - destPoint.col) === 2)) { 51 | if (board.reverted) { 52 | currentMove += (destPoint.col < 2 53 | ? 'O-O' 54 | : 'O-O-O'); 55 | } else { 56 | currentMove += destPoint.col < 3 57 | ? 'O-O-O' 58 | : 'O-O'; 59 | } 60 | } else { 61 | if (!(sourcePiece instanceof Pawn) && possibleCaptures.length === 0 && possibleMoves.length < 2) { // Nf3 62 | currentMove += MoveUtils.getFirstLetterPiece(sourcePiece) + MoveUtils.formatSingle( 63 | destPoint, 64 | board.reverted 65 | ); 66 | } else { 67 | if (possibleMoves && possibleMoves.length === 2 && possibleCaptures.length === 0) { // Nbd7 68 | if (this.isEqualByCol( 69 | possibleMoves[0], 70 | possibleMoves[1] 71 | )) { 72 | currentMove += MoveUtils.getFirstLetterPiece( 73 | sourcePiece) + MoveUtils.reverse( 74 | board, 75 | sourcePiece.point.row 76 | ) + MoveUtils.formatSingle( 77 | destPoint, 78 | board.reverted 79 | ); 80 | } else { 81 | currentMove += MoveUtils.getFirstLetterPiece( 82 | sourcePiece) + MoveUtils.formatCol( 83 | board, 84 | sourcePiece.point.col 85 | ) + MoveUtils.formatSingle( 86 | destPoint, 87 | board.reverted 88 | ); 89 | } 90 | } else { 91 | if (possibleCaptures.length > 1) { 92 | if ((this.isEqualByCol( 93 | possibleCaptures[0], 94 | possibleCaptures[1] 95 | ))) { 96 | currentMove += MoveUtils.getFirstLetterPiece( 97 | sourcePiece) + MoveUtils.reverse( 98 | board, 99 | sourcePiece.point.row 100 | ) + 'x' + MoveUtils.formatSingle( 101 | destPoint, 102 | board.reverted 103 | ); 104 | } else { 105 | currentMove += MoveUtils.getFirstLetterPiece( 106 | sourcePiece) + MoveUtils.formatCol( 107 | board, 108 | sourcePiece.point.col 109 | ) + 'x' + MoveUtils.formatSingle( 110 | destPoint, 111 | board.reverted 112 | ); 113 | } 114 | } else { 115 | currentMove += MoveUtils.getFirstLetterPiece( 116 | sourcePiece) + 'x' + MoveUtils.formatSingle( 117 | destPoint, board.reverted 118 | ); 119 | } 120 | } 121 | } 122 | } 123 | } 124 | } 125 | this.pgn.push(currentMove); 126 | } 127 | 128 | private resolvePieceByFirstChar(move: string, piece: Piece) { 129 | return MoveUtils.getFirstLetterPiece(piece) === move; 130 | } 131 | 132 | private isEqualByCol(aPiece: Piece, bPiece: Piece) { 133 | return aPiece.point.col === bPiece.point.col; 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/utils/move-utils.ts: -------------------------------------------------------------------------------- 1 | import { Board } from '../models/board'; 2 | import { Bishop } from '../models/pieces/bishop'; 3 | import { Color } from '../models/pieces/color'; 4 | import { King } from '../models/pieces/king'; 5 | import { Knight } from '../models/pieces/knight'; 6 | import { Pawn } from '../models/pieces/pawn'; 7 | import { Piece } from '../models/pieces/piece'; 8 | import { Point } from '../models/pieces/point'; 9 | import { MoveTranslation } from '../models/move-translation'; 10 | import { Queen } from '../models/pieces/queen'; 11 | import { Rook } from '../models/pieces/rook'; 12 | 13 | export class MoveUtils { 14 | public static willMoveCauseCheck( 15 | currentColor: Color, 16 | row: number, 17 | col: number, 18 | destRow: number, 19 | destCol: number, 20 | board: Board 21 | ) { 22 | const srcPiece = board.getPieceByField(row, col); 23 | const destPiece = board.getPieceByField(destRow, destCol); 24 | 25 | if (srcPiece) { 26 | srcPiece.point.row = destRow; 27 | srcPiece.point.col = destCol; 28 | } 29 | 30 | if (destPiece) { 31 | board.pieces = board.pieces.filter((piece) => piece !== destPiece); 32 | } 33 | const isBound = board.isKingInCheck(currentColor, board.pieces); 34 | 35 | if (srcPiece) { 36 | srcPiece.point.col = col; 37 | srcPiece.point.row = row; 38 | } 39 | 40 | if (destPiece) { 41 | board.pieces.push(destPiece); 42 | } 43 | 44 | return isBound; 45 | } 46 | 47 | public static format( 48 | sourcePoint: Point, 49 | destPoint: Point, 50 | reverted: boolean 51 | ) { 52 | if (reverted) { 53 | const sourceX = 104 - sourcePoint.col; 54 | const destX = 104 - destPoint.col; 55 | return ( 56 | String.fromCharCode(sourceX) + 57 | (sourcePoint.row + 1) + 58 | String.fromCharCode(destX) + 59 | (destPoint.row + 1) 60 | ); 61 | } else { 62 | const incrementX = 97; 63 | return ( 64 | String.fromCharCode(sourcePoint.col + incrementX) + 65 | (Math.abs(sourcePoint.row - 7) + 1) + 66 | String.fromCharCode(destPoint.col + incrementX) + 67 | (Math.abs(destPoint.row - 7) + 1) 68 | ); 69 | } 70 | } 71 | 72 | public static translateCoordsToIndex(coords: string, reverted: boolean) { 73 | let xAxis: number; 74 | let yAxis: number; 75 | if (reverted) { 76 | xAxis = 104 - coords.charCodeAt(0); 77 | yAxis = +coords.charAt(1) - 1; 78 | } else { 79 | xAxis = coords.charCodeAt(0) - 97; 80 | yAxis = Math.abs(+coords.charAt(1) - 8); 81 | } 82 | 83 | return new MoveTranslation(xAxis, yAxis, reverted); 84 | } 85 | 86 | public static findPieceByPossibleMovesContaining( 87 | coords: string, 88 | board: Board, 89 | color: Color 90 | ): Piece[] { 91 | let indexes = this.translateCoordsToIndex(coords, board.reverted); 92 | let destPoint = new Point(indexes.yAxis, indexes.xAxis); 93 | let foundPieces = []; 94 | 95 | for (let piece of board.pieces.filter(piece => piece.color === color)) { 96 | for (let point of piece.getPossibleMoves()) { 97 | if (!MoveUtils.willMoveCauseCheck( 98 | piece.color, 99 | piece.point.row, 100 | piece.point.col, 101 | indexes.yAxis, 102 | indexes.xAxis, 103 | board 104 | ) && point.isEqual(destPoint)) { 105 | foundPieces.push(piece); 106 | } 107 | } 108 | } 109 | return foundPieces; 110 | } 111 | 112 | public static findPieceByPossibleCapturesContaining( 113 | coords: string, 114 | board: Board, 115 | color: Color 116 | ): Piece[] { 117 | let indexes = this.translateCoordsToIndex(coords, board.reverted); 118 | let destPoint = new Point(indexes.yAxis, indexes.xAxis); 119 | let foundPieces = []; 120 | for (let piece of board.pieces.filter(piece => piece.color === color)) { 121 | for (let point of piece.getPossibleCaptures()) { 122 | if (!MoveUtils.willMoveCauseCheck( 123 | piece.color, 124 | piece.point.row, 125 | piece.point.col, 126 | indexes.yAxis, 127 | indexes.xAxis, 128 | board 129 | ) && point.isEqual(destPoint)) { 130 | foundPieces.push(piece); 131 | } 132 | } 133 | } 134 | 135 | return foundPieces; 136 | } 137 | 138 | public static formatSingle(point: Point, reverted: boolean): string { 139 | if (reverted) { 140 | const sourceX = 104 - point.col; 141 | return ( 142 | String.fromCharCode(sourceX) + 143 | (point.row + 1) 144 | ); 145 | } else { 146 | const incrementX = 97; 147 | return ( 148 | String.fromCharCode(point.col + incrementX) + 149 | (Math.abs(point.row - 7) + 1) 150 | ); 151 | } 152 | } 153 | 154 | public static getFirstLetterPiece(piece: Piece): string { 155 | if (piece instanceof Pawn) { 156 | return 'P'; 157 | } else { 158 | if (piece instanceof Knight) { 159 | return 'N'; 160 | } else { 161 | if (piece instanceof Bishop) { 162 | return 'B'; 163 | } else { 164 | if (piece instanceof Rook) { 165 | return 'R'; 166 | } else { 167 | if (piece instanceof King) { 168 | return 'K'; 169 | } else { 170 | if (piece instanceof Queen) { 171 | return 'Q'; 172 | } 173 | } 174 | } 175 | } 176 | } 177 | } 178 | 179 | return ''; 180 | } 181 | 182 | static reverse(board: Board, row: number) { 183 | return board.reverted 184 | ? row + 1 185 | : Math.abs(row - 7) + 1; 186 | } 187 | 188 | static formatCol(board: Board, col: number): string { 189 | return board.reverted 190 | ? String.fromCharCode(104 - col) 191 | : String.fromCharCode(97 + col); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/models/pieces/bishop.ts: -------------------------------------------------------------------------------- 1 | import { PieceConstant } from '../../utils/unicode-constants'; 2 | import { Board } from '../board'; 3 | import { Color } from './color'; 4 | import { King } from './king'; 5 | import { Piece } from './piece'; 6 | import { Point } from './point'; 7 | 8 | export class Bishop extends Piece { 9 | constructor( 10 | point: Point, 11 | color: Color, 12 | constant: PieceConstant, 13 | board: Board 14 | ) { 15 | super(point, color, constant, 3, board); 16 | } 17 | 18 | getPossibleMoves(): Point[] { 19 | const possiblePoints = []; 20 | 21 | const row = this.point.row; 22 | const col = this.point.col; 23 | 24 | for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; --i, --j) { 25 | // lewa gorna przekatna 26 | if (this.board.isFieldEmpty(i, j)) { 27 | possiblePoints.push(new Point(i, j)); 28 | } else { 29 | break; 30 | } 31 | } 32 | 33 | for (let i = row - 1, j = col + 1; i >= 0 && j < 8; --i, ++j) { 34 | // prawa gorna przekatna 35 | if (this.board.isFieldEmpty(i, j)) { 36 | possiblePoints.push(new Point(i, j)); 37 | } else { 38 | break; 39 | } 40 | } 41 | 42 | for (let i = row + 1, j = col - 1; i < 8 && j >= 0; ++i, --j) { 43 | // lewa dolna przekatna 44 | if (this.board.isFieldEmpty(i, j)) { 45 | possiblePoints.push(new Point(i, j)); 46 | } else { 47 | break; 48 | } 49 | } 50 | 51 | for (let i = row + 1, j = col + 1; i < 8 && j < 8; ++i, ++j) { 52 | // prawa dolna przekatna 53 | if (this.board.isFieldEmpty(i, j)) { 54 | possiblePoints.push(new Point(i, j)); 55 | } else { 56 | break; 57 | } 58 | } 59 | 60 | return possiblePoints; 61 | } 62 | 63 | getPossibleCaptures() { 64 | const possiblePoints = []; 65 | 66 | const row = this.point.row; 67 | const col = this.point.col; 68 | 69 | for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; --i, --j) { 70 | // lewa gorna przekatna 71 | if ( 72 | this.board.isFieldTakenByEnemy( 73 | i, 74 | j, 75 | this.color === Color.WHITE ? Color.BLACK : Color.WHITE 76 | ) 77 | ) { 78 | possiblePoints.push(new Point(i, j)); 79 | break; 80 | } else { 81 | if (!this.board.isFieldEmpty(i, j)) { 82 | break; 83 | } 84 | } 85 | } 86 | 87 | for (let i = row - 1, j = col + 1; i >= 0 && j < 8; --i, ++j) { 88 | // prawa gorna przekatna 89 | if ( 90 | this.board.isFieldTakenByEnemy( 91 | i, 92 | j, 93 | this.color === Color.WHITE ? Color.BLACK : Color.WHITE 94 | ) 95 | ) { 96 | possiblePoints.push(new Point(i, j)); 97 | break; 98 | } else { 99 | if (!this.board.isFieldEmpty(i, j)) { 100 | break; 101 | } 102 | } 103 | } 104 | 105 | for (let i = row + 1, j = col - 1; i < 8 && j >= 0; ++i, --j) { 106 | // lewa dolna przekatna 107 | if ( 108 | this.board.isFieldTakenByEnemy( 109 | i, 110 | j, 111 | this.color === Color.WHITE ? Color.BLACK : Color.WHITE 112 | ) 113 | ) { 114 | possiblePoints.push(new Point(i, j)); 115 | break; 116 | } else { 117 | if (!this.board.isFieldEmpty(i, j)) { 118 | break; 119 | } 120 | } 121 | } 122 | 123 | for (let i = row + 1, j = col + 1; i < 8 && j < 8; ++i, ++j) { 124 | // prawa dolna przekatna 125 | if ( 126 | this.board.isFieldTakenByEnemy( 127 | i, 128 | j, 129 | this.color === Color.WHITE ? Color.BLACK : Color.WHITE 130 | ) 131 | ) { 132 | possiblePoints.push(new Point(i, j)); 133 | break; 134 | } else { 135 | if (!this.board.isFieldEmpty(i, j)) { 136 | break; 137 | } 138 | } 139 | } 140 | 141 | return possiblePoints; 142 | } 143 | 144 | getCoveredFields(): Point[] { 145 | const possiblePoints = []; 146 | 147 | const row = this.point.row; 148 | const col = this.point.col; 149 | 150 | for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; --i, --j) { 151 | // lewa gorna przekatna 152 | if (this.board.isFieldEmpty(i, j)) { 153 | possiblePoints.push(new Point(i, j)); 154 | } else { 155 | if (!(this.board.getPieceByField(i, j) instanceof King)) { 156 | possiblePoints.push(new Point(i, j)); 157 | } 158 | break; 159 | } 160 | } 161 | 162 | for (let i = row - 1, j = col + 1; i >= 0 && j < 8; --i, ++j) { 163 | // prawa gorna przekatna 164 | if (this.board.isFieldEmpty(i, j)) { 165 | possiblePoints.push(new Point(i, j)); 166 | } else { 167 | if (!(this.board.getPieceByField(i, j) instanceof King)) { 168 | possiblePoints.push(new Point(i, j)); 169 | } 170 | break; 171 | } 172 | } 173 | 174 | for (let i = row + 1, j = col - 1; i < 8 && j >= 0; ++i, --j) { 175 | // lewa dolna przekatna 176 | if (this.board.isFieldEmpty(i, j)) { 177 | possiblePoints.push(new Point(i, j)); 178 | } else { 179 | if (!(this.board.getPieceByField(i, j) instanceof King)) { 180 | possiblePoints.push(new Point(i, j)); 181 | } 182 | break; 183 | } 184 | } 185 | 186 | for (let i = row + 1, j = col + 1; i < 8 && j < 8; ++i, ++j) { 187 | // prawa dolna przekatna 188 | if (this.board.isFieldEmpty(i, j)) { 189 | possiblePoints.push(new Point(i, j)); 190 | } else { 191 | if (!(this.board.getPieceByField(i, j) instanceof King)) { 192 | possiblePoints.push(new Point(i, j)); 193 | } 194 | break; 195 | } 196 | } 197 | 198 | return possiblePoints; 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 |

ngx-chess-board example

3 | 11 | 12 |
13 | 14 |
15 |
16 | 17 |
18 | 20 |
21 |
22 | 23 |
24 |
25 | 26 |
27 | 29 |
30 | 31 |
32 | 33 |
34 | 36 |
37 | px 38 |
39 |
40 |
41 | 42 |
43 | 46 | 47 | 50 | 51 | 54 | 55 | 58 |
59 | 60 | 64 | Piece dragging is {{ dragDisabled ? 'OFF' : 'ON'}} 65 |
66 | 70 | Drawing with right mouse button is {{ drawDisabled ? 'OFF' : 'ON'}} 71 |
72 | 80 | Light tiles are {{ lightDisabled ? 'OFF' : 'ON' }} 81 |
82 | 90 | Dark tiles are {{ darkDisabled ? 'OFF' : 'ON' }} 91 |
92 | 101 | Free mode is {{ freeMode ? 'ON' : 'OFF' }} 102 |
103 | 104 |
105 | 106 | 108 |
109 | 110 |
111 | 114 | 115 | 118 |
119 | 120 |
121 | 122 | 124 |
125 | 126 |
127 | 130 | 131 | 134 | 135 | 138 | 139 |
140 | 141 |
142 | 143 | 144 |
145 |
146 | 154 | 161 |
162 | 163 |
164 | 165 | 166 |
167 |
168 | 176 | 184 | 188 | 196 |
197 |
198 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/engine/board-state-provider/board-loader/notation-processors/fen-loader/default-fen-processor.ts: -------------------------------------------------------------------------------- 1 | import { Board } from '../../../../../models/board'; 2 | import { Bishop } from '../../../../../models/pieces/bishop'; 3 | import { Color } from '../../../../../models/pieces/color'; 4 | import { King } from '../../../../../models/pieces/king'; 5 | import { Knight } from '../../../../../models/pieces/knight'; 6 | import { Pawn } from '../../../../../models/pieces/pawn'; 7 | import { Point } from '../../../../../models/pieces/point'; 8 | import { Queen } from '../../../../../models/pieces/queen'; 9 | import { Rook } from '../../../../../models/pieces/rook'; 10 | import { UnicodeConstants } from '../../../../../utils/unicode-constants'; 11 | import { AbstractEngineFacade } from '../../../../abstract-engine-facade'; 12 | import { NotationProcessor } from '../notation-processor'; 13 | 14 | export class DefaultFenProcessor implements NotationProcessor { 15 | 16 | public process(notation: string, engineFacade: AbstractEngineFacade): void { 17 | let fen = notation; 18 | if (notation) { 19 | engineFacade.board.reverted = false; 20 | engineFacade.board.pieces = []; 21 | const split = fen.split('/'); 22 | for (let i = 0; i < 8; ++i) { 23 | let pointer = 0; 24 | for (let j = 0; j < split[i].split(' ')[0].length; ++j) { 25 | const chunk = split[i].charAt(j); 26 | if (chunk.match(/[0-9]/)) { 27 | pointer += Number(chunk); 28 | } else { 29 | switch (chunk) { 30 | case 'r': 31 | engineFacade.board.pieces.push( 32 | new Rook( 33 | new Point(i, pointer), 34 | Color.BLACK, 35 | UnicodeConstants.BLACK_ROOK, 36 | engineFacade.board 37 | ) 38 | ); 39 | break; 40 | case 'n': 41 | engineFacade.board.pieces.push( 42 | new Knight( 43 | new Point(i, pointer), 44 | Color.BLACK, 45 | UnicodeConstants.BLACK_KNIGHT, 46 | engineFacade.board 47 | ) 48 | ); 49 | 50 | break; 51 | case 'b': 52 | engineFacade.board.pieces.push( 53 | new Bishop( 54 | new Point(i, pointer), 55 | Color.BLACK, 56 | UnicodeConstants.BLACK_BISHOP, 57 | engineFacade.board 58 | ) 59 | ); 60 | break; 61 | case 'q': 62 | engineFacade.board.pieces.push( 63 | new Queen( 64 | new Point(i, pointer), 65 | Color.BLACK, 66 | UnicodeConstants.BLACK_QUEEN, 67 | engineFacade.board 68 | ) 69 | ); 70 | break; 71 | case 'k': 72 | engineFacade.board.pieces.push( 73 | new King( 74 | new Point(i, pointer), 75 | Color.BLACK, 76 | UnicodeConstants.BLACK_KING, 77 | engineFacade.board 78 | ) 79 | ); 80 | break; 81 | case 'p': { 82 | const pawn = new Pawn( 83 | new Point(i, pointer), 84 | Color.BLACK, 85 | UnicodeConstants.BLACK_PAWN, 86 | engineFacade.board 87 | ); 88 | if ( 89 | (pawn.color === Color.BLACK && pawn.point.row !== 1) || 90 | (pawn.color === Color.WHITE && pawn.point.row !== 6) 91 | ) { 92 | pawn.isMovedAlready = true; 93 | } 94 | engineFacade.board.pieces.push(pawn); 95 | break; 96 | } 97 | case 'R': 98 | engineFacade.board.pieces.push( 99 | new Rook( 100 | new Point(i, pointer), 101 | Color.WHITE, 102 | UnicodeConstants.WHITE_ROOK, 103 | engineFacade.board 104 | ) 105 | ); 106 | 107 | break; 108 | case 'N': 109 | engineFacade.board.pieces.push( 110 | new Knight( 111 | new Point(i, pointer), 112 | Color.WHITE, 113 | UnicodeConstants.WHITE_KNIGHT, 114 | engineFacade.board 115 | ) 116 | ); 117 | break; 118 | 119 | case 'B': 120 | engineFacade.board.pieces.push( 121 | new Bishop( 122 | new Point(i, pointer), 123 | Color.WHITE, 124 | UnicodeConstants.WHITE_BISHOP, 125 | engineFacade.board 126 | ) 127 | ); 128 | break; 129 | 130 | case 'Q': 131 | engineFacade.board.pieces.push( 132 | new Queen( 133 | new Point(i, pointer), 134 | Color.WHITE, 135 | UnicodeConstants.WHITE_QUEEN, 136 | engineFacade.board 137 | ) 138 | ); 139 | break; 140 | 141 | case 'K': 142 | engineFacade.board.pieces.push( 143 | new King( 144 | new Point(i, pointer), 145 | Color.WHITE, 146 | UnicodeConstants.WHITE_KING, 147 | engineFacade.board 148 | ) 149 | ); 150 | break; 151 | 152 | case 'P': { 153 | const pawn = new Pawn( 154 | new Point(i, pointer), 155 | Color.WHITE, 156 | UnicodeConstants.WHITE_PAWN, 157 | engineFacade.board 158 | ); 159 | if ( 160 | (pawn.color === Color.BLACK && pawn.point.row !== 1) || 161 | (pawn.color === Color.WHITE && pawn.point.row !== 6) 162 | ) { 163 | pawn.isMovedAlready = true; 164 | } 165 | engineFacade.board.pieces.push(pawn); 166 | break; 167 | } 168 | } 169 | ++pointer; 170 | } 171 | } 172 | } 173 | 174 | this.setCurrentPlayer(engineFacade.board, fen); 175 | this.setCastles(engineFacade.board, fen); 176 | this.setEnPassant(fen); 177 | this.setFullMoveCount(fen); 178 | engineFacade.board.fen = fen; 179 | } else { 180 | throw Error('Incorrect FEN provided'); 181 | } 182 | } 183 | 184 | 185 | private setCurrentPlayer(board: Board, fen: string) { 186 | if (fen) { 187 | const split = fen.split(' '); 188 | board.currentWhitePlayer = split[1] === 'w'; 189 | } 190 | } 191 | 192 | private setCastles(board: Board, fen: string) { 193 | if (fen) { 194 | const split = fen.split(' '); 195 | const castleChunk = split[2]; 196 | 197 | if (!castleChunk.includes('K')) { 198 | this.setRookAlreadyMoved(board, Color.WHITE, 7); 199 | } 200 | 201 | if (!castleChunk.includes('Q')) { 202 | this.setRookAlreadyMoved(board, Color.WHITE, 0); 203 | } 204 | 205 | if (!castleChunk.includes('k')) { 206 | this.setRookAlreadyMoved(board, Color.BLACK, 7); 207 | } 208 | 209 | if (!castleChunk.includes('q')) { 210 | this.setRookAlreadyMoved(board, Color.BLACK, 0); 211 | } 212 | } 213 | } 214 | 215 | private setFullMoveCount(fen: string) {} 216 | 217 | private setEnPassant(fen: string) { 218 | if (fen) { 219 | const split = fen.split(' '); 220 | const enPassantPoint = split[3]; 221 | 222 | if (enPassantPoint === '-') { 223 | return; 224 | } 225 | 226 | // if() 227 | } 228 | } 229 | 230 | private setRookAlreadyMoved(board: Board, color: Color, col: number) { 231 | const rook = board.pieces.find( 232 | (piece) => piece.color === color && piece instanceof Rook && piece.point.col === col 233 | ) as Rook; 234 | 235 | if (rook) { 236 | rook.isMovedAlready = true; 237 | } 238 | } 239 | 240 | } 241 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/ngx-chess-board.component.ts: -------------------------------------------------------------------------------- 1 | import { CdkDragEnd, CdkDragMove, CdkDragStart } from '@angular/cdk/drag-drop'; 2 | import { 3 | AfterViewInit, 4 | Component, 5 | ElementRef, 6 | EventEmitter, 7 | HostListener, 8 | Input, 9 | OnChanges, 10 | OnInit, 11 | Output, 12 | SimpleChanges, 13 | ViewChild, 14 | } from '@angular/core'; 15 | import { AbstractEngineFacade } from './engine/abstract-engine-facade'; 16 | import { BoardLoader } from './engine/board-state-provider/board-loader/board-loader'; 17 | import { 18 | NotationProcessorFactory, 19 | NotationType, 20 | } from './engine/board-state-provider/board-loader/notation-processors/notation-processor-factory'; 21 | import { ClickUtils } from './engine/click/click-utils'; 22 | import { EngineFacade } from './engine/engine-facade'; 23 | import { MoveChange } from './engine/outputs/move-change/move-change'; 24 | import { HistoryMove } from './history-move-provider/history-move'; 25 | import { Board } from './models/board'; 26 | import { Piece } from './models/pieces/piece'; 27 | import { NgxChessBoardView } from './ngx-chess-board-view'; 28 | import { PiecePromotionModalComponent } from './piece-promotion/piece-promotion-modal/piece-promotion-modal.component'; 29 | import { Constants } from './utils/constants'; 30 | import { PieceIconInput } from './utils/inputs/piece-icon-input'; 31 | import { PieceIconInputManager } from './utils/inputs/piece-icon-input-manager'; 32 | import { ColorInput, PieceTypeInput } from './utils/inputs/piece-type-input'; 33 | 34 | @Component({ 35 | selector: 'ngx-chess-board', 36 | templateUrl: './ngx-chess-board.component.html', 37 | styleUrls: ['./ngx-chess-board.component.scss'], 38 | }) 39 | export class NgxChessBoardComponent 40 | implements OnInit, OnChanges, NgxChessBoardView, AfterViewInit { 41 | 42 | @Input() darkTileColor = Constants.DEFAULT_DARK_TILE_COLOR; 43 | @Input() lightTileColor: string = Constants.DEFAULT_LIGHT_TILE_COLOR; 44 | @Input() showCoords = true; 45 | @Input() sourcePointColor: string = Constants.DEFAULT_SOURCE_POINT_COLOR; 46 | @Input() destinationPointColor: string = Constants.DEFAULT_DESTINATION_POINT_COLOR; 47 | @Input() legalMovesPointColor: string = Constants.DEFAULT_LEGAL_MOVE_POINT_COLOR; 48 | @Input() showLastMove = true; 49 | @Input() showLegalMoves = true; 50 | @Input() showActivePiece = true; 51 | @Input() animationDuration = 200; 52 | @Input() showPossibleCaptures = true; 53 | /** 54 | * Enabling free mode removes turn-based restriction and allows to move any piece freely! 55 | */ 56 | @Output() moveChange = new EventEmitter(); 57 | @Output() checkmate = new EventEmitter(); 58 | @Output() stalemate = new EventEmitter(); 59 | 60 | @ViewChild('boardRef') 61 | boardRef: ElementRef; 62 | @ViewChild('modal') 63 | modal: PiecePromotionModalComponent; 64 | 65 | pieceSize: number; 66 | selected = false; 67 | boardLoader: BoardLoader; 68 | pieceIconManager: PieceIconInputManager; 69 | isDragging = false; 70 | startTransition = ''; 71 | 72 | engineFacade: AbstractEngineFacade; 73 | 74 | randomId = (Math.random() + 1).toString(36).substring(7); 75 | 76 | constructor() { 77 | this.engineFacade = new EngineFacade( 78 | new Board(), 79 | this.moveChange 80 | ); 81 | } 82 | 83 | @Input('size') 84 | public set size(size: number) { 85 | if ( 86 | size && 87 | size >= Constants.MIN_BOARD_SIZE && 88 | size <= Constants.MAX_BOARD_SIZE 89 | ) { 90 | this.engineFacade.heightAndWidth = size; 91 | } else { 92 | this.engineFacade.heightAndWidth = Constants.DEFAULT_SIZE; 93 | } 94 | this.engineFacade.drawProvider.clear(); 95 | this.calculatePieceSize(); 96 | } 97 | 98 | @Input('freeMode') 99 | public set freeMode(freeMode: boolean) { 100 | this.engineFacade.freeMode = freeMode; 101 | } 102 | 103 | @Input('dragDisabled') 104 | public set dragDisabled(dragDisabled: boolean) { 105 | this.engineFacade.dragDisabled = dragDisabled; 106 | } 107 | 108 | @Input('drawDisabled') 109 | public set drawDisabled(drawDisabled: boolean) { 110 | this.engineFacade.drawDisabled = drawDisabled; 111 | } 112 | 113 | @Input('pieceIcons') 114 | public set pieceIcons(pieceIcons: PieceIconInput) { 115 | this.engineFacade.pieceIconManager.pieceIconInput = pieceIcons; 116 | } 117 | 118 | @Input('lightDisabled') 119 | public set lightDisabled(lightDisabled: boolean) { 120 | this.engineFacade.lightDisabled = lightDisabled; 121 | } 122 | 123 | @Input('darkDisabled') 124 | public set darkDisabled(darkDisabled: boolean) { 125 | this.engineFacade.darkDisabled = darkDisabled; 126 | } 127 | 128 | @HostListener('contextmenu', ['$event']) 129 | onRightClick(event: MouseEvent) { 130 | event.preventDefault(); 131 | } 132 | 133 | ngOnChanges(changes: SimpleChanges) { 134 | if ( 135 | (changes.lightDisabled && 136 | this.lightDisabled && 137 | this.engineFacade.board.currentWhitePlayer) || 138 | (changes.darkDisabled && 139 | this.darkDisabled && 140 | !this.engineFacade.board.currentWhitePlayer) 141 | ) { 142 | this.engineFacade.board.possibleCaptures = []; 143 | this.engineFacade.board.possibleMoves = []; 144 | } 145 | } 146 | 147 | ngOnInit() {} 148 | 149 | ngAfterViewInit(): void { 150 | this.engineFacade.modal = this.modal; 151 | this.calculatePieceSize(); 152 | } 153 | 154 | onMouseUp(event: MouseEvent) { 155 | this.engineFacade.onMouseUp( 156 | event, 157 | this.getClickPoint(event), 158 | this.boardRef.nativeElement.getBoundingClientRect().left, 159 | this.boardRef.nativeElement.getBoundingClientRect().top 160 | ); 161 | } 162 | 163 | reverse(): void { 164 | this.selected = false; 165 | this.engineFacade.board.reverse(); 166 | this.engineFacade.coords.reverse(); 167 | } 168 | 169 | updateBoard = (board: Board) => { 170 | this.engineFacade.board = board; 171 | this.engineFacade.board.possibleCaptures = []; 172 | this.engineFacade.board.possibleMoves = []; 173 | this.boardLoader = new BoardLoader(this.engineFacade); 174 | this.boardLoader.setEngineFacade(this.engineFacade); 175 | }; 176 | 177 | setFEN(fen: string): void { 178 | try { 179 | this.engineFacade.boardLoader.setNotationProcessor( 180 | NotationProcessorFactory.getProcessor(NotationType.FEN) 181 | ); 182 | this.engineFacade.boardLoader.loadFEN(fen); 183 | this.engineFacade.board.possibleCaptures = []; 184 | this.engineFacade.board.possibleMoves = []; 185 | this.engineFacade.coords.reset(); 186 | } catch (exception) { 187 | this.engineFacade.boardLoader.addPieces(); 188 | } 189 | } 190 | 191 | setPGN(pgn: string): void { 192 | try { 193 | this.engineFacade.pgnProcessor.reset(); 194 | this.engineFacade.boardLoader.setNotationProcessor( 195 | NotationProcessorFactory.getProcessor(NotationType.PGN) 196 | ); 197 | this.engineFacade.boardLoader.loadPGN(pgn); 198 | this.engineFacade.board.possibleCaptures = []; 199 | this.engineFacade.board.possibleMoves = []; 200 | this.engineFacade.coords.reset(); 201 | } catch (exception) { 202 | console.log(exception); 203 | this.engineFacade.boardLoader.addPieces(); 204 | } 205 | } 206 | 207 | getFEN(): string { 208 | return this.engineFacade.board.fen; 209 | } 210 | 211 | dragEnded(event: CdkDragEnd): void { 212 | this.isDragging = false; 213 | this.engineFacade.dragEndStrategy.process( 214 | event, 215 | this.engineFacade.moveDone, 216 | this.startTransition 217 | ); 218 | } 219 | 220 | dragStart(event: CdkDragStart): void { 221 | this.isDragging = true; 222 | let trans = event.source.getRootElement().style.transform.split(') '); 223 | // this.startTrans= trans; 224 | this.startTransition = trans.length === 2 ? trans[1] : trans[0]; 225 | this.engineFacade.dragStartStrategy.process(event); 226 | } 227 | 228 | onMouseDown(event: MouseEvent) { 229 | this.engineFacade.onMouseDown(event, this.getClickPoint(event), 230 | this.boardRef.nativeElement.getBoundingClientRect().left, 231 | this.boardRef.nativeElement.getBoundingClientRect().top 232 | ); 233 | } 234 | 235 | onContextMenu(event: MouseEvent): void { 236 | this.engineFacade.onContextMenu(event); 237 | } 238 | 239 | getClickPoint(event) { 240 | return ClickUtils.getClickPoint( 241 | event, 242 | this.boardRef.nativeElement.getBoundingClientRect().top, 243 | this.boardRef.nativeElement.getBoundingClientRect().height, 244 | this.boardRef.nativeElement.getBoundingClientRect().left, 245 | this.boardRef.nativeElement.getBoundingClientRect().width 246 | ); 247 | } 248 | 249 | private calculatePieceSize() { 250 | this.pieceSize = this.engineFacade.heightAndWidth / 8; 251 | } 252 | 253 | 254 | getCustomPieceIcons(piece: Piece) { 255 | return JSON.parse( 256 | `{ "background-image": "url('${this.engineFacade.pieceIconManager.getPieceIcon( 257 | piece 258 | )}')"}` 259 | ); 260 | } 261 | 262 | move(coords: string): void { 263 | this.engineFacade.move(coords); 264 | } 265 | 266 | getMoveHistory(): HistoryMove[] { 267 | return this.engineFacade.getMoveHistory(); 268 | } 269 | 270 | reset(): void { 271 | this.engineFacade.reset(); 272 | } 273 | 274 | undo(): void { 275 | this.engineFacade.undo(); 276 | } 277 | 278 | addPiece( 279 | pieceTypeInput: PieceTypeInput, 280 | colorInput: ColorInput, 281 | coords: string 282 | ) { 283 | this.engineFacade.addPiece(pieceTypeInput, colorInput, coords); 284 | } 285 | 286 | getPGN() { 287 | return this.engineFacade.pgnProcessor.getPGN(); 288 | } 289 | 290 | dragMoved($event: CdkDragMove) { 291 | let x = ($event.pointerPosition.x - $event.source.getRootElement().parentElement.getBoundingClientRect().left) - (this.pieceSize / 2); 292 | let y = ($event.pointerPosition.y - $event.source.getRootElement().parentElement.getBoundingClientRect().top) - (this.pieceSize / 2); 293 | $event.source.getRootElement().style.transform = 'translate3d(' + x + 'px, ' 294 | + (y) + 'px,0px)'; 295 | } 296 | 297 | getTileBackgroundColor(i, j): string { 298 | let color = ((i + j) % 2 === 0) ? this.lightTileColor : this.darkTileColor; 299 | 300 | if (this.showLastMove) { 301 | if (this.engineFacade.board.isXYInSourceMove(i, j)) { 302 | color = this.sourcePointColor; 303 | } 304 | 305 | if (this.engineFacade.board.isXYInDestMove(i, j)) { 306 | color = this.destinationPointColor; 307 | } 308 | } 309 | 310 | return color; 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /projects/ngx-chess-board/src/lib/models/board.ts: -------------------------------------------------------------------------------- 1 | import { cloneDeep } from 'lodash'; 2 | import { Bishop } from './pieces/bishop'; 3 | import { Color } from './pieces/color'; 4 | import { King } from './pieces/king'; 5 | import { Knight } from './pieces/knight'; 6 | import { Pawn } from './pieces/pawn'; 7 | import { Piece } from './pieces/piece'; 8 | import { Point } from './pieces/point'; 9 | import { Queen } from './pieces/queen'; 10 | import { Rook } from './pieces/rook'; 11 | 12 | export class Board { 13 | board: number[][] = []; 14 | pieces: Piece[] = []; 15 | 16 | enPassantPoint: Point = null; 17 | enPassantPiece: Piece = null; 18 | lastMoveSrc: Point = null; 19 | lastMoveDest: Point = null; 20 | activePiece: Piece; 21 | 22 | blackKingChecked: boolean; 23 | possibleCaptures: any[] = []; 24 | possibleMoves: Point[] = []; 25 | whiteKingChecked: boolean; 26 | 27 | currentWhitePlayer = true; 28 | reverted = false; 29 | fullMoveCount = 1; 30 | fen: string; 31 | 32 | constructor() { 33 | for (let i = 0; i < 8; ++i) { 34 | this.board[i] = []; 35 | for (let j = 0; j < 8; ++j) { 36 | this.board[i][j] = 0; 37 | } 38 | } 39 | } 40 | 41 | isXYInPossibleMoves(row: number, col: number): boolean { 42 | return this.possibleMoves.some((move) => move.row === row && move.col === col); 43 | } 44 | 45 | isXYInPossibleCaptures(row: number, col: number): boolean { 46 | return this.possibleCaptures.some((capture) => capture.row === row && capture.col === col); 47 | } 48 | 49 | isXYInSourceMove(i: number, j: number) { 50 | return this.lastMoveSrc && this.lastMoveSrc.row === i && this.lastMoveSrc.col === j; 51 | } 52 | 53 | isXYInDestMove(i: number, j: number) { 54 | return this.lastMoveDest && this.lastMoveDest.row === i && this.lastMoveDest.col === j; 55 | } 56 | 57 | isXYInActiveMove(i: number, j: number) { 58 | return this.activePiece && this.activePiece.point.row === i && this.activePiece.point.col === j; 59 | } 60 | 61 | isPointInPossibleMoves(point: Point): boolean { 62 | return this.possibleMoves.some((move) => move.row === point.row && move.col === point.col); 63 | } 64 | 65 | isPointInPossibleCaptures(point: Point): boolean { 66 | return this.possibleCaptures.some((capture) => capture.row === point.row && capture.col === point.col); 67 | } 68 | 69 | reset() { 70 | this.lastMoveDest = null; 71 | this.lastMoveSrc = null; 72 | this.whiteKingChecked = false; 73 | this.blackKingChecked = false; 74 | this.possibleCaptures = []; 75 | this.possibleMoves = []; 76 | this.activePiece = null; 77 | this.reverted = false; 78 | this.currentWhitePlayer = true; 79 | this.enPassantPoint = null; 80 | this.enPassantPiece = null; 81 | this.fullMoveCount = 1; 82 | this.calculateFEN(); 83 | } 84 | 85 | reverse() { 86 | this.reverted = !this.reverted; 87 | this.activePiece = null; 88 | this.possibleMoves = []; 89 | this.possibleCaptures = []; 90 | 91 | this.pieces.forEach((piece: Piece) => this.reversePoint(piece.point)); 92 | 93 | this.reversePoint(this.lastMoveSrc); 94 | this.reversePoint(this.lastMoveDest); 95 | 96 | if (this.enPassantPoint && this.enPassantPiece) { 97 | this.reversePoint(this.enPassantPoint); 98 | } 99 | } 100 | 101 | clone(): Board { 102 | return cloneDeep(this); 103 | } 104 | 105 | isFieldTakenByEnemy(row: number, col: number, enemyColor: Color): boolean { 106 | if (row > 7 || row < 0 || col > 7 || col < 0) { 107 | return false; 108 | } 109 | return this.pieces.some( 110 | (piece) => piece.point.col === col && piece.point.row === row && piece.color === enemyColor 111 | ); 112 | } 113 | 114 | isFieldEmpty(row: number, col: number): boolean { 115 | if (row > 7 || row < 0 || col > 7 || col < 0) { 116 | return false; 117 | } 118 | return !this.pieces.some((piece) => piece.point.col === col && piece.point.row === row); 119 | } 120 | 121 | isFieldUnderAttack(row: number, col: number, color: Color) { 122 | return this.pieces 123 | .filter((piece) => piece.color === color) 124 | .some((piece) => piece.getCoveredFields().some((field) => field.col === col && field.row === row)); 125 | } 126 | 127 | getPieceByField(row: number, col: number): Piece { 128 | if (this.isFieldEmpty(row, col)) { 129 | // throw new Error('Piece not found'); 130 | return undefined; 131 | } 132 | 133 | return this.pieces.find((piece) => piece.point.col === col && piece.point.row === row); 134 | } 135 | 136 | isKingInCheck(color: Color, pieces: Piece[]): boolean { 137 | const king = pieces.find((piece) => piece.color === color && piece instanceof King); 138 | 139 | if (king) { 140 | return pieces.some( 141 | (piece) => 142 | piece 143 | .getPossibleCaptures() 144 | .some((point) => point.col === king.point.col && point.row === king.point.row) && 145 | piece.color !== color 146 | ); 147 | } 148 | return false; 149 | } 150 | 151 | getKingByColor(color: Color): King { 152 | return this.pieces.find((piece) => piece instanceof King && piece.color === color) as King; 153 | } 154 | 155 | getCastleFENString(color: Color) { 156 | const king = this.getKingByColor(color); 157 | 158 | if (!king || king.isMovedAlready) { 159 | return ''; 160 | } 161 | 162 | let fen = ''; 163 | const leftRook = this.getPieceByField(king.point.row, 0); 164 | const rightRook = this.getPieceByField(king.point.row, 7); 165 | 166 | if (rightRook instanceof Rook && rightRook.color === color) { 167 | if (!rightRook.isMovedAlready) { 168 | fen += this.reverted ? 'q' : 'k'; 169 | } 170 | } 171 | 172 | if (leftRook instanceof Rook && leftRook.color === color) { 173 | if (!leftRook.isMovedAlready) { 174 | fen += this.reverted ? 'k' : 'q'; 175 | } 176 | } 177 | 178 | fen = fen.split('').sort().join(''); 179 | return color === Color.BLACK ? fen : fen.toUpperCase(); 180 | } 181 | 182 | getEnPassantFENString() { 183 | if (this.enPassantPoint) { 184 | if (this.reverted) { 185 | return String.fromCharCode(104 - this.enPassantPoint.col) + (this.enPassantPoint.row + 1); 186 | } else { 187 | return String.fromCharCode(97 + this.enPassantPoint.col) + (Math.abs(this.enPassantPoint.row - 7) + 1); 188 | } 189 | } else { 190 | return '-'; 191 | } 192 | } 193 | 194 | calculateFEN() { 195 | let fen = ''; 196 | for (let i = 0; i < 8; ++i) { 197 | let emptyFields = 0; 198 | for (let j = 0; j < 8; ++j) { 199 | const foundPiece = this.pieces.find((piece) => piece.point.col === j && piece.point.row === i); 200 | if (foundPiece) { 201 | if (emptyFields > 0) { 202 | fen += emptyFields; 203 | emptyFields = 0; 204 | } 205 | 206 | if (foundPiece instanceof Rook) { 207 | fen += foundPiece.color === Color.BLACK ? 'r' : 'R'; 208 | } else { 209 | if (foundPiece instanceof Knight) { 210 | fen += foundPiece.color === Color.BLACK ? 'n' : 'N'; 211 | } else { 212 | if (foundPiece instanceof Bishop) { 213 | fen += foundPiece.color === Color.BLACK ? 'b' : 'B'; 214 | } else { 215 | if (foundPiece instanceof Queen) { 216 | fen += foundPiece.color === Color.BLACK ? 'q' : 'Q'; 217 | } else { 218 | if (foundPiece instanceof King) { 219 | fen += foundPiece.color === Color.BLACK ? 'k' : 'K'; 220 | } else { 221 | if (foundPiece instanceof Pawn) { 222 | fen += foundPiece.color === Color.BLACK ? 'p' : 'P'; 223 | } 224 | } 225 | } 226 | } 227 | } 228 | } 229 | } else { 230 | ++emptyFields; 231 | } 232 | } 233 | 234 | if (emptyFields > 0) { 235 | fen += emptyFields; 236 | } 237 | 238 | fen += '/'; 239 | } 240 | 241 | fen = fen.substr(0, fen.length - 1); 242 | 243 | if (this.reverted) { 244 | fen = fen.split('').reverse().join(''); 245 | } 246 | 247 | fen += ' ' + (this.currentWhitePlayer ? 'w' : 'b'); 248 | const whiteEnPassant = this.getCastleFENString(Color.WHITE); 249 | const blackEnPassant = this.getCastleFENString(Color.BLACK); 250 | let concatedEnPassant = whiteEnPassant + blackEnPassant; 251 | if (!concatedEnPassant) { 252 | concatedEnPassant = '-'; 253 | } 254 | 255 | fen += ' ' + concatedEnPassant; 256 | fen += ' ' + this.getEnPassantFENString(); 257 | fen += ' ' + 0; 258 | fen += ' ' + this.fullMoveCount; 259 | this.fen = fen; 260 | } 261 | 262 | isXYInPointSelection(i: number, j: number) { 263 | return false; 264 | } 265 | 266 | private reversePoint(point: Point) { 267 | if (point) { 268 | point.row = Math.abs(point.row - 7); 269 | point.col = Math.abs(point.col - 7); 270 | } 271 | } 272 | 273 | public getPieceByPoint(row: number, col: number): Piece { 274 | row = Math.floor(row); 275 | col = Math.floor(col); 276 | return this.pieces.find( 277 | (piece) => piece.point.col === col && piece.point.row === row 278 | ); 279 | } 280 | 281 | public checkIfPawnTakesEnPassant(newPoint: Point) { 282 | if (newPoint.isEqual(this.enPassantPoint)) { 283 | this.pieces = this.pieces.filter( 284 | (piece) => piece !== this.enPassantPiece 285 | ); 286 | this.enPassantPoint = null; 287 | this.enPassantPiece = null; 288 | } 289 | } 290 | 291 | public checkIfPawnEnpassanted(piece: Pawn, newPoint: Point) { 292 | if (Math.abs(piece.point.row - newPoint.row) > 1) { 293 | this.enPassantPiece = piece; 294 | this.enPassantPoint = new Point( 295 | (piece.point.row + newPoint.row) / 2, 296 | piece.point.col 297 | ); 298 | } else { 299 | this.enPassantPoint = null; 300 | this.enPassantPiece = null; 301 | } 302 | } 303 | 304 | isKingChecked(piece: Piece) { 305 | if (piece instanceof King) { 306 | return piece.color === Color.WHITE 307 | ? this.whiteKingChecked 308 | : this.blackKingChecked; 309 | } 310 | } 311 | 312 | getCurrentPlayerColor(): number { 313 | return this.currentWhitePlayer ? Color.WHITE : Color.BLACK; 314 | } 315 | } 316 | --------------------------------------------------------------------------------