Skip to content

Commit

Permalink
[#135] Redesigned piece movement validation logic
Browse files Browse the repository at this point in the history
  • Loading branch information
Ido-Barnea committed Jan 12, 2024
1 parent d127cde commit 738a538
Show file tree
Hide file tree
Showing 15 changed files with 360 additions and 264 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
}
],
"comma-dangle": ["error", "always-multiline"],
"camelcase": ["error"]
"camelcase": ["error"],
"no-constant-condition": "off"
}
}
6 changes: 2 additions & 4 deletions development/code/logic/PieceLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,8 @@ function validatePlayerAction(
if (draggedPiece === target) return false;
if (draggedPiece.position.boardId !== target.position.boardId) return false;

const targetPosition = draggedPiece.validateMove(target);
if (comparePositions(targetPosition, draggedPiece.position)) return false;

return true;
const validMoves = draggedPiece.getValidMoves();
return validMoves.some(position => comparePositions(position, target.position));
}

function handleTargetType(
Expand Down
8 changes: 4 additions & 4 deletions development/code/logic/pieces/Bishop.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ describe('Piece movements', () => {
coordinates: [2, 3],
boardId: OVERWORLD_BOARD_ID,
};
const validMove = bishop.validateMove({ position: newPosition });
expect(validMove).toEqual(newPosition);
let validMoves = bishop.getValidMoves();
expect(validMoves).toContain(newPosition);

const invalidPosition: Position = {
coordinates: [0, 0],
boardId: OVERWORLD_BOARD_ID,
};
const invalidMove = bishop.validateMove({ position: invalidPosition });
expect(invalidMove).toEqual(initialPosition);
validMoves = bishop.getValidMoves();
expect(validMoves).not.toContain(invalidPosition);
});
});
64 changes: 45 additions & 19 deletions development/code/logic/pieces/Bishop.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { bishopResource } from '../../ui/Resources';
import { Piece } from './Pieces';
import { Player, PlayerColors } from '../Players';
import { Position, Square, simulateMove } from './PiecesUtilities';
import { Position } from './PiecesUtilities';
import { getPieceByPosition } from '../Utilities';

export class Bishop extends Piece {
constructor(position: Position, player: Player) {
Expand All @@ -11,26 +12,51 @@ export class Bishop extends Piece {
super(position, player, bishopResource, 'Bishop', logo);
}

validateMove(target: Piece | Square): Position {
const targetCoordinates = target.position.coordinates;
getValidMoves(): Array<Position> {
const validMoves: Array<Position> = [];
const currentCoordinates = this.position.coordinates;
const stepX = targetCoordinates[0] > currentCoordinates[0] ? 1 : -1;
const stepY = targetCoordinates[1] > currentCoordinates[1] ? 1 : -1;

const absDeltaX = Math.abs(targetCoordinates[0] - currentCoordinates[0]);
const absDeltaY = Math.abs(targetCoordinates[1] - currentCoordinates[1]);

// Bishops can only move diagonally.
if (absDeltaY === absDeltaX) {
return simulateMove(
this,
target.position,
stepX,
stepY,
-1,
);

// Iterate over all possible directions (diagonals) for the bishop
const directions = [
{ deltaX: 1, deltaY: 1 },
{ deltaX: 1, deltaY: -1 },
{ deltaX: -1, deltaY: 1 },
{ deltaX: -1, deltaY: -1 },
];

for (const direction of directions) {
let stepX = direction.deltaX;
let stepY = direction.deltaY;

// Iterate until the edge of the board or another piece is encountered
while (true) {
const nextX = currentCoordinates[0] + stepX;
const nextY = currentCoordinates[1] + stepY;

// Check if the next position is within the board boundaries
if (nextX < 0 || nextX >= 8 || nextY < 0 || nextY >= 8) {
break;
}

const nextPosition: Position = {
coordinates: [nextX, nextY],
boardId: this.position.boardId,
};

// Add the position to the list of valid moves
validMoves.push(nextPosition);

// If the move encounters another piece, stop iterating in this direction
if (getPieceByPosition(nextPosition)) {
break;
}

// Move further in the current direction
stepX += direction.deltaX;
stepY += direction.deltaY;
}
}

return this.position;
return validMoves;
}
}
16 changes: 6 additions & 10 deletions development/code/logic/pieces/King.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,21 @@ describe('Piece movements', () => {
coordinates: [4, 6],
boardId: OVERWORLD_BOARD_ID,
};
const validStraightMove = king.validateMove({
position: newStraightPosition,
});
expect(validStraightMove).toEqual(newStraightPosition);
let validMoves = king.getValidMoves();
expect(validMoves).toContain(newStraightPosition);

const newDiagonalPosition: Position = {
coordinates: [5, 8],
boardId: OVERWORLD_BOARD_ID,
};
const validDiagonalMove = king.validateMove({
position: newDiagonalPosition,
});
expect(validDiagonalMove).toEqual(newDiagonalPosition);
validMoves = king.getValidMoves();
expect(validMoves).toContain(newDiagonalPosition);

const invalidPosition: Position = {
coordinates: [0, 0],
boardId: OVERWORLD_BOARD_ID,
};
const invalidMove = king.validateMove({ position: invalidPosition });
expect(invalidMove).toEqual(initialPosition);
validMoves = king.getValidMoves();
expect(validMoves).not.toContain(invalidPosition);
});
});
166 changes: 100 additions & 66 deletions development/code/logic/pieces/King.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { kingResource } from '../../ui/Resources';
import { Piece } from './Pieces';
import { Player, PlayerColors } from '../Players';
import { Position, Square, simulateMove } from './PiecesUtilities';
import { comparePositions } from '../Utilities';
import { Position } from './PiecesUtilities';
import { getPieceByPosition } from '../Utilities';
import { Rook } from './Rook';
import { game } from '../../Game';

export class King extends Piece {
Expand All @@ -14,80 +15,113 @@ export class King extends Piece {
super(position, player, kingResource, 'King', logo);
}

validateMove(target: Piece | Square): Position {
const targetCoordinates = target.position.coordinates;
getRookForCastling(player: Player, kingside: boolean): Rook | undefined {
const rank = player.color === PlayerColors.WHITE ? 0 : 7;

if (kingside) {
// Kingside castling
const kingsideRookPosition: Position = {
coordinates: [7, rank],
boardId: this.position.boardId,
};

return getPieceByPosition(kingsideRookPosition) as Rook | undefined;
} else {
// Queenside castling
const queensideRookPosition: Position = {
coordinates: [0, rank],
boardId: this.position.boardId,
};

return getPieceByPosition(queensideRookPosition) as Rook | undefined;
}
}

isPathClear(start: Position, end: Position): boolean {
const deltaX = Math.sign(end.coordinates[0] - start.coordinates[0]);
const deltaY = Math.sign(end.coordinates[1] - start.coordinates[1]);

let currentX = start.coordinates[0] + deltaX;
let currentY = start.coordinates[1] + deltaY;

while (currentX !== end.coordinates[0] || currentY !== end.coordinates[1]) {
if (getPieceByPosition({ coordinates: [currentX, currentY], boardId: this.position.boardId })) {
return false;
}

currentX += deltaX;
currentY += deltaY;
}

if (getPieceByPosition({ coordinates: [currentX, currentY], boardId: this.position.boardId })) {
return false;
}

return true;
}

getValidMoves(): Array<Position> {
const validMoves: Array<Position> = [];
const currentCoordinates = this.position.coordinates;

const stepX =
targetCoordinates[0] > currentCoordinates[0]
? 1
: targetCoordinates[0] < currentCoordinates[0]
? -1
: 0;
const stepY =
targetCoordinates[1] > currentCoordinates[1]
? 1
: targetCoordinates[1] < currentCoordinates[1]
? -1
: 0;

const deltaX = targetCoordinates[0] - currentCoordinates[0];
const deltaY = targetCoordinates[1] - currentCoordinates[1];

const absDeltaX = Math.abs(deltaX);
const absDeltaY = Math.abs(deltaY);

// King can only move one step but in any direction.
if (absDeltaX === 1 || absDeltaY === 1) {
return simulateMove(
this,
target.position,
stepX,
stepY,
1,
);
// Define possible directions for the king to move
const directions = [
{ deltaX: 1, deltaY: 0 },
{ deltaX: 1, deltaY: 1 },
{ deltaX: 0, deltaY: 1 },
{ deltaX: -1, deltaY: 1 },
{ deltaX: -1, deltaY: 0 },
{ deltaX: -1, deltaY: -1 },
{ deltaX: 0, deltaY: -1 },
{ deltaX: 1, deltaY: -1 },
];

for (const direction of directions) {
const nextX = currentCoordinates[0] + direction.deltaX;
const nextY = currentCoordinates[1] + direction.deltaY;

// Check if the next position is within the board boundaries
if (nextX >= 0 && nextX < 8 && nextY >= 0 && nextY < 8) {
const nextPosition: Position = {
coordinates: [nextX, nextY],
boardId: this.position.boardId,
};

// Add the position to the list of valid moves
validMoves.push(nextPosition);
}
}

// Check for castling
if (absDeltaX === 2 && absDeltaY === 0 && !this.hasMoved) {
let destinationPosition = this.position;
// Moved two squares horizontally and didn't move before
if (deltaX === 2) {
// Kingside castling
destinationPosition = simulateMove(
this,
target.position,
stepX,
stepY,
2,
false,
);
} else {
// Queenside castling
// Queenside castling needs to check an extra square
const targetPosition: Position = {
coordinates: [
target.position.coordinates[0] - 1,
target.position.coordinates[1],
],
boardId: target.position.boardId,
if (!this.hasMoved) {
const kingsideRook = this.getRookForCastling(this.player, true);
const queensideRook = this.getRookForCastling(this.player, false);

// Kingside castling
if (kingsideRook && !kingsideRook.hasMoved) {
const kingsideTargetPosition: Position = {
coordinates: [currentCoordinates[0] + 2, currentCoordinates[1]],
boardId: this.position.boardId,
};
destinationPosition = simulateMove(
this,
targetPosition,
stepX,
stepY,
3,
false,
);
if (this.isPathClear(this.position, kingsideTargetPosition)) {
game.switchIsCastling();
validMoves.push(kingsideTargetPosition);
}
}

if (!comparePositions(destinationPosition, this.position)) {
game.switchIsCastling();
// Queenside castling
if (queensideRook && !queensideRook.hasMoved) {
const queensideTargetPosition: Position = {
coordinates: [currentCoordinates[0] - 2, currentCoordinates[1]],
boardId: this.position.boardId,
};
if (this.isPathClear(this.position, queensideTargetPosition)) {
game.switchIsCastling();
validMoves.push(queensideTargetPosition);
}
}
return destinationPosition;
}

return this.position;
return validMoves;
}
}
8 changes: 4 additions & 4 deletions development/code/logic/pieces/Knight.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ describe('Piece movements', () => {
coordinates: [2, 5],
boardId: OVERWORLD_BOARD_ID,
};
const validMove = knight.validateMove({ position: newPosition });
expect(validMove).toEqual(newPosition);
let validMoves = knight.getValidMoves();
expect(validMoves).toContain(newPosition);

const invalidPosition: Position = {
coordinates: [1, 5],
boardId: OVERWORLD_BOARD_ID,
};
const invalidMove = knight.validateMove({ position: invalidPosition });
expect(invalidMove).toEqual(initialPosition);
validMoves = knight.getValidMoves();
expect(validMoves).not.toContain(invalidPosition);
});
});
Loading

0 comments on commit 738a538

Please sign in to comment.