feat: added web view 1v1
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
.board-shell {
|
||||
width: min(100%, 82dvh, 760px);
|
||||
max-width: calc(100dvw - 2rem);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.board-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, 1fr);
|
||||
border: 2px solid #5A2C28;
|
||||
border-radius: 10px 10px 0 0;
|
||||
overflow: hidden;
|
||||
background: #5A2C28;
|
||||
}
|
||||
|
||||
.square {
|
||||
position: relative;
|
||||
aspect-ratio: 1 / 1;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
overflow: hidden;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.square.light {
|
||||
background-image: url('/arabian-chess/sprites/board/board_square_white.png');
|
||||
}
|
||||
|
||||
.square.dark {
|
||||
background-image: url('/arabian-chess/sprites/board/board_square_black.png');
|
||||
}
|
||||
|
||||
.square.highlighted::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 28%;
|
||||
height: 28%;
|
||||
border-radius: 50%;
|
||||
background: rgba(193, 158, 245, 0.75);
|
||||
border: 2px solid #5A2C28;
|
||||
}
|
||||
|
||||
.square.selected {
|
||||
outline: 3px solid #BA6D4B;
|
||||
outline-offset: -3px;
|
||||
}
|
||||
|
||||
.board-bottom {
|
||||
width: 100%;
|
||||
display: block;
|
||||
border: 2px solid #5A2C28;
|
||||
border-top: 0;
|
||||
border-radius: 0 0 10px 10px;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<div class="board-shell">
|
||||
<div class="board-grid">
|
||||
@for (square of squares; track trackByCoordinate($index, square)) {
|
||||
<button
|
||||
type="button"
|
||||
class="square"
|
||||
[class.light]="square.isLight"
|
||||
[class.dark]="!square.isLight"
|
||||
[class.selected]="isSelected(square)"
|
||||
[class.highlighted]="isHighlighted(square)"
|
||||
[attr.data-square]="square.coordinate"
|
||||
(click)="onSquareClick(square)"
|
||||
>
|
||||
<app-chess-piece [pieceCode]="square.pieceCode" />
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
<img class="board-bottom" src="/arabian-chess/sprites/board/board_bottom.png" alt="Board frame" />
|
||||
</div>
|
||||
@@ -0,0 +1,90 @@
|
||||
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
|
||||
import { ChessPieceComponent } from '../chess-piece/chess-piece.component';
|
||||
|
||||
interface BoardSquare {
|
||||
coordinate: string;
|
||||
isLight: boolean;
|
||||
pieceCode: string | null;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-chess-board',
|
||||
standalone: true,
|
||||
imports: [ChessPieceComponent],
|
||||
templateUrl: './chess-board.component.html',
|
||||
styleUrl: './chess-board.component.css'
|
||||
})
|
||||
export class ChessBoardComponent implements OnChanges {
|
||||
@Input({ required: true }) fen = '';
|
||||
@Input() selectedSquare: string | null = null;
|
||||
@Input() highlightedSquares: string[] = [];
|
||||
@Output() squareSelected = new EventEmitter<string>();
|
||||
|
||||
squares: BoardSquare[] = [];
|
||||
private highlightedSquareSet = new Set<string>();
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes['fen']) {
|
||||
this.squares = this.buildSquares(this.fen);
|
||||
}
|
||||
|
||||
if (changes['highlightedSquares']) {
|
||||
this.highlightedSquareSet = new Set(this.highlightedSquares);
|
||||
}
|
||||
}
|
||||
|
||||
trackByCoordinate(_index: number, square: BoardSquare): string {
|
||||
return square.coordinate;
|
||||
}
|
||||
|
||||
onSquareClick(square: BoardSquare): void {
|
||||
this.squareSelected.emit(square.coordinate);
|
||||
}
|
||||
|
||||
isSelected(square: BoardSquare): boolean {
|
||||
return this.selectedSquare === square.coordinate;
|
||||
}
|
||||
|
||||
isHighlighted(square: BoardSquare): boolean {
|
||||
return this.highlightedSquareSet.has(square.coordinate);
|
||||
}
|
||||
|
||||
private buildSquares(fen: string): BoardSquare[] {
|
||||
const placement = fen.split(' ')[0] ?? '';
|
||||
const rows = placement.split('/');
|
||||
if (rows.length !== 8) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const squares: BoardSquare[] = [];
|
||||
|
||||
rows.forEach((row, rowIndex) => {
|
||||
let column = 0;
|
||||
|
||||
for (const char of row) {
|
||||
if (char >= '1' && char <= '8') {
|
||||
const emptyCount = Number(char);
|
||||
for (let step = 0; step < emptyCount; step += 1) {
|
||||
squares.push(this.createSquare(rowIndex, column, null));
|
||||
column += 1;
|
||||
}
|
||||
} else {
|
||||
squares.push(this.createSquare(rowIndex, column, char));
|
||||
column += 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return squares;
|
||||
}
|
||||
|
||||
private createSquare(rowIndex: number, column: number, pieceCode: string | null): BoardSquare {
|
||||
const file = String.fromCharCode(97 + column);
|
||||
const rank = String(8 - rowIndex);
|
||||
return {
|
||||
coordinate: `${file}${rank}`,
|
||||
isLight: (rowIndex + column) % 2 === 0,
|
||||
pieceCode
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
.piece {
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
display: block;
|
||||
object-fit: contain;
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
@if (pieceCode) {
|
||||
<img class="piece" [src]="spriteUrl" [alt]="pieceCode" />
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-chess-piece',
|
||||
standalone: true,
|
||||
templateUrl: './chess-piece.component.html',
|
||||
styleUrl: './chess-piece.component.css'
|
||||
})
|
||||
export class ChessPieceComponent {
|
||||
@Input({ required: true }) pieceCode: string | null = null;
|
||||
|
||||
get spriteUrl(): string {
|
||||
if (!this.pieceCode) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const color = this.pieceCode === this.pieceCode.toUpperCase() ? 'white' : 'black';
|
||||
const pieceName = this.getPieceName(this.pieceCode.toLowerCase());
|
||||
return `/arabian-chess/sprites/pieces/${color}_${pieceName}.png`;
|
||||
}
|
||||
|
||||
private getPieceName(piece: string): string {
|
||||
switch (piece) {
|
||||
case 'k':
|
||||
return 'king';
|
||||
case 'q':
|
||||
return 'queen';
|
||||
case 'r':
|
||||
return 'rook';
|
||||
case 'b':
|
||||
return 'bishop';
|
||||
case 'n':
|
||||
return 'knight';
|
||||
case 'p':
|
||||
return 'pawn';
|
||||
default:
|
||||
return 'pawn';
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user