Skip to content

Instantly share code, notes, and snippets.

@bcherny
Created March 24, 2018 21:43
Show Gist options
  • Save bcherny/b5ed05cf767b40f768bfc1c1860aa6e0 to your computer and use it in GitHub Desktop.
Save bcherny/b5ed05cf767b40f768bfc1c1860aa6e0 to your computer and use it in GitHub Desktop.
void function() {
class ChessError extends Error { }
class InvalidMoveError extends ChessError { }
class NoLineOfSightError extends ChessError { }
class NotYourTurnError extends ChessError { }
// Represents a chess game
class Game {
private board = new Board
private pieces = Game.makePieces()
private turn = 'White'
tryToMove(piece: Piece, file: File, rank: Rank) {
if (piece.getColor() !== this.turn) {
return new NotYourTurnError
}
let canMove = piece.canMoveTo(file, rank)
let canTake = piece.canTakeAt(file, rank)
let pieceToTake = this.getPieceAt(file, rank)
// Is there a piece there already?
if (canTake && pieceToTake) {
this.take(piece, pieceToTake)
this.nextTurn()
return
}
// Can the piece move there?
if (!canMove) {
return new InvalidMoveError
}
// Is there a piece in the way?
if (!piece.canJump && !this.hasLineOfSight(piece, file, rank)) {
return new NoLineOfSightError
}
// Move!
this.move(piece, file, rank)
this.nextTurn()
}
private move(piece: Piece, file: File, rank: Rank) {
piece.position = { file, rank }
}
private take(piece: Piece, otherPiece: Piece) {
otherPiece.isAlive = false
piece.position = otherPiece.position
}
private nextTurn() {
this.turn = this.turn === 'White' ? 'Black' : 'White'
}
private hasLineOfSight(piece: Piece, file: File, rank: Rank) {
return piece
.getPathsTo(file, rank)
.some(path =>
!path.some(_ => this.getPieceAt(_.file, _.rank) !== undefined)
)
}
private getPieceAt(file: File, rank: Rank): Piece | undefined {
return this.pieces.find(_ =>
_.isAlive &&
_.position.file === file && _.position.rank === rank
)
}
private static makePieces() {
return [
// kings
new King('White', 'E', 1),
new King('Black', 'E', 8),
// queens
new Queen('White', 'D', 1),
new Queen('Black', 'D', 8),
// bishops
new Bishop('White', 'C', 1),
new Bishop('White', 'F', 1),
new Bishop('Black', 'C', 8),
new Bishop('Black', 'F', 8),
// knights
new Knight('White', 'B', 1),
new Knight('White', 'G', 1),
new Knight('Black', 'B', 8),
new Knight('Black', 'G', 8),
// rooks
new Rook('White', 'A', 1),
new Rook('White', 'H', 1),
new Rook('Black', 'A', 8),
new Rook('Black', 'H', 8),
// pawns
new Pawn('White', 'A', 1),
new Pawn('White', 'B', 1),
new Pawn('White', 'C', 1),
new Pawn('White', 'D', 1),
new Pawn('White', 'E', 1),
new Pawn('White', 'F', 1),
new Pawn('White', 'G', 1),
new Pawn('White', 'H', 1),
new Pawn('Black', 'A', 8),
new Pawn('Black', 'B', 8),
new Pawn('Black', 'C', 8),
new Pawn('Black', 'D', 8),
new Pawn('Black', 'E', 8),
new Pawn('Black', 'F', 8),
new Pawn('Black', 'G', 8),
new Pawn('Black', 'H', 8)
]
}
}
// A chess board
class Board { }
// A chess piece
abstract class Piece {
abstract canJump: boolean
constructor(
private color: Color,
protected file: File,
protected rank: Rank,
private status: Status = 'Alive'
) { }
getColor() {
return this.color
}
get isAlive() {
return this.status === 'Alive'
}
set isAlive(isAlive: boolean) {
this.status = isAlive ? 'Alive' : 'Dead'
}
get position() {
return {
file: this.file,
rank: this.rank
}
}
set position(position: Position) {
this.rank = position.rank
this.file = position.file
}
moveTo(file: File, rank: Rank) {
this.file = file
this.rank = rank
}
canTakeAt(file: File, rank: Rank) {
return this.canMoveTo(file, rank)
}
abstract canMoveTo(file: File, rank: Rank): boolean
abstract getPathsTo(file: File, rank: Rank): Position[][]
}
class King extends Piece {
canJump = false
canMoveTo(file: File, rank: Rank) {
return Math.abs(rank - this.rank) < 2
&& Math.abs(file.charCodeAt(0) - this.file.charCodeAt(0)) < 2
}
getPathsTo(file: File, rank: Rank) {
if (!this.canMoveTo(file, rank)) {
return []
}
return [
[{ file, rank }]
]
}
}
class Queen extends Piece {
canJump = false
canMoveTo(file: File, rank: Rank) {
return file === this.file
|| rank === this.rank
|| Math.abs(file.charCodeAt(0) - this.file.charCodeAt(0)) === Math.abs(rank - this.rank)
}
}
class Bishop extends Piece {
canJump = false
canMoveTo(file: File, rank: Rank) {
return Math.abs(file.charCodeAt(0) - this.file.charCodeAt(0)) === Math.abs(rank - this.rank)
}
}
class Knight extends Piece {
canJump = true
canMoveTo(file: File, rank: Rank) {
let x = Math.abs(file.charCodeAt(0) - this.file.charCodeAt(0))
let y = Math.abs(rank - this.rank)
return x === 2 && y === 3 || x === 3 && y === 2
}
}
class Rook extends Piece {
canJump = false
canMoveTo(file: File, rank: Rank) {
return file === this.file
|| rank === this.rank
}
}
class Pawn extends Piece {
canJump = false
canMoveTo(file: File, rank: Rank) {
if (this.file !== file) {
return false
}
if (this.rank === 2 && rank === 4) {
return true
}
if (this.rank + 1 === rank) {
return true
}
return false
}
canTakeAt(file: File, rank: Rank) {
return this.rank + 1 === rank
&& Math.abs(this.file.charCodeAt(0) - file.charCodeAt(0)) === 1
}
}
type Color = 'Black' | 'White'
type File = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H'
type Rank = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8
type Status = 'Dead' | 'Alive'
type Position = {
rank: Rank
file: File
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment