mirror of
https://github.com/lichess-org/scalachess.git
synced 2026-05-26 13:50:54 +00:00
scalafmt align.preset = none
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
||||
version = "3.9.8"
|
||||
runner.dialect = scala3
|
||||
|
||||
align.preset = more
|
||||
align.preset = none
|
||||
maxColumn = 110
|
||||
spaces.inImportCurlyBraces = true
|
||||
rewrite.rules = [SortModifiers, AvoidInfix]
|
||||
|
||||
@@ -23,9 +23,9 @@ class BinaryFenBench:
|
||||
private val Work: Long = 10
|
||||
|
||||
@Param(Array("10", "100", "1000"))
|
||||
var games: Int = scala.compiletime.uninitialized
|
||||
var games: Int = scala.compiletime.uninitialized
|
||||
var sits: List[Position.AndFullMoveNumber] = scala.compiletime.uninitialized
|
||||
var fens: List[BinaryFen] = scala.compiletime.uninitialized
|
||||
var fens: List[BinaryFen] = scala.compiletime.uninitialized
|
||||
|
||||
@Setup
|
||||
def setup(): Unit =
|
||||
@@ -41,7 +41,7 @@ class BinaryFenBench:
|
||||
@Benchmark
|
||||
def write(bh: Blackhole) =
|
||||
val games = this.sits
|
||||
var i = 0
|
||||
var i = 0
|
||||
while i < games.size do
|
||||
val game = games(i)
|
||||
Blackhole.consumeCPU(Work)
|
||||
@@ -51,7 +51,7 @@ class BinaryFenBench:
|
||||
@Benchmark
|
||||
def read(bh: Blackhole) =
|
||||
val games = this.fens
|
||||
var i = 0
|
||||
var i = 0
|
||||
while i < games.size do
|
||||
val fen = games(i)
|
||||
Blackhole.consumeCPU(Work)
|
||||
|
||||
@@ -23,14 +23,14 @@ class DestinationsBench:
|
||||
@Param(Array("100"))
|
||||
var games: Int = scala.compiletime.uninitialized
|
||||
|
||||
var threecheckInput: List[Position] = scala.compiletime.uninitialized
|
||||
var antichessInput: List[Position] = scala.compiletime.uninitialized
|
||||
var atomicInput: List[Position] = scala.compiletime.uninitialized
|
||||
var crazyhouseInput: List[Position] = scala.compiletime.uninitialized
|
||||
var threecheckInput: List[Position] = scala.compiletime.uninitialized
|
||||
var antichessInput: List[Position] = scala.compiletime.uninitialized
|
||||
var atomicInput: List[Position] = scala.compiletime.uninitialized
|
||||
var crazyhouseInput: List[Position] = scala.compiletime.uninitialized
|
||||
var racingkingsInput: List[Position] = scala.compiletime.uninitialized
|
||||
var hordeInput: List[Position] = scala.compiletime.uninitialized
|
||||
var randomInput: List[Position] = scala.compiletime.uninitialized
|
||||
var trickyInput: List[Position] = scala.compiletime.uninitialized
|
||||
var hordeInput: List[Position] = scala.compiletime.uninitialized
|
||||
var randomInput: List[Position] = scala.compiletime.uninitialized
|
||||
var trickyInput: List[Position] = scala.compiletime.uninitialized
|
||||
|
||||
@Setup
|
||||
def setup(): Unit =
|
||||
|
||||
@@ -23,14 +23,14 @@ class FenReaderBench:
|
||||
@Param(Array("100"))
|
||||
var games: Int = scala.compiletime.uninitialized
|
||||
|
||||
var threecheckInput: List[(Variant, FullFen)] = scala.compiletime.uninitialized
|
||||
var antichessInput: List[(Variant, FullFen)] = scala.compiletime.uninitialized
|
||||
var atomicInput: List[(Variant, FullFen)] = scala.compiletime.uninitialized
|
||||
var crazyhouseInput: List[(Variant, FullFen)] = scala.compiletime.uninitialized
|
||||
var threecheckInput: List[(Variant, FullFen)] = scala.compiletime.uninitialized
|
||||
var antichessInput: List[(Variant, FullFen)] = scala.compiletime.uninitialized
|
||||
var atomicInput: List[(Variant, FullFen)] = scala.compiletime.uninitialized
|
||||
var crazyhouseInput: List[(Variant, FullFen)] = scala.compiletime.uninitialized
|
||||
var racingkingsInput: List[(Variant, FullFen)] = scala.compiletime.uninitialized
|
||||
var hordeInput: List[(Variant, FullFen)] = scala.compiletime.uninitialized
|
||||
var randomInput: List[(Variant, FullFen)] = scala.compiletime.uninitialized
|
||||
var trickyInput: List[(Variant, FullFen)] = scala.compiletime.uninitialized
|
||||
var hordeInput: List[(Variant, FullFen)] = scala.compiletime.uninitialized
|
||||
var randomInput: List[(Variant, FullFen)] = scala.compiletime.uninitialized
|
||||
var trickyInput: List[(Variant, FullFen)] = scala.compiletime.uninitialized
|
||||
|
||||
@Setup
|
||||
def setup(): Unit =
|
||||
|
||||
@@ -23,14 +23,14 @@ class FenWriterBench:
|
||||
@Param(Array("100"))
|
||||
var games: Int = scala.compiletime.uninitialized
|
||||
|
||||
var threecheckInput: List[Game] = scala.compiletime.uninitialized
|
||||
var antichessInput: List[Game] = scala.compiletime.uninitialized
|
||||
var atomicInput: List[Game] = scala.compiletime.uninitialized
|
||||
var crazyhouseInput: List[Game] = scala.compiletime.uninitialized
|
||||
var threecheckInput: List[Game] = scala.compiletime.uninitialized
|
||||
var antichessInput: List[Game] = scala.compiletime.uninitialized
|
||||
var atomicInput: List[Game] = scala.compiletime.uninitialized
|
||||
var crazyhouseInput: List[Game] = scala.compiletime.uninitialized
|
||||
var racingkingsInput: List[Game] = scala.compiletime.uninitialized
|
||||
var hordeInput: List[Game] = scala.compiletime.uninitialized
|
||||
var randomInput: List[Game] = scala.compiletime.uninitialized
|
||||
var trickyInput: List[Game] = scala.compiletime.uninitialized
|
||||
var hordeInput: List[Game] = scala.compiletime.uninitialized
|
||||
var randomInput: List[Game] = scala.compiletime.uninitialized
|
||||
var trickyInput: List[Game] = scala.compiletime.uninitialized
|
||||
|
||||
@Setup
|
||||
def setup(): Unit =
|
||||
|
||||
@@ -25,14 +25,14 @@ class PerftBench:
|
||||
@Param(Array("10000", "100000", "1000000", "10000000"))
|
||||
var nodes: Long = scala.compiletime.uninitialized
|
||||
|
||||
var threecheckPerfts: List[Perft] = scala.compiletime.uninitialized
|
||||
var antichessPerfts: List[Perft] = scala.compiletime.uninitialized
|
||||
var atomicPerfts: List[Perft] = scala.compiletime.uninitialized
|
||||
var crazyhousePerfts: List[Perft] = scala.compiletime.uninitialized
|
||||
var threecheckPerfts: List[Perft] = scala.compiletime.uninitialized
|
||||
var antichessPerfts: List[Perft] = scala.compiletime.uninitialized
|
||||
var atomicPerfts: List[Perft] = scala.compiletime.uninitialized
|
||||
var crazyhousePerfts: List[Perft] = scala.compiletime.uninitialized
|
||||
var racingkingsPerfts: List[Perft] = scala.compiletime.uninitialized
|
||||
var hordePerfts: List[Perft] = scala.compiletime.uninitialized
|
||||
var randomPerfts: List[Perft] = scala.compiletime.uninitialized
|
||||
var trickyPerfts: List[Perft] = scala.compiletime.uninitialized
|
||||
var hordePerfts: List[Perft] = scala.compiletime.uninitialized
|
||||
var randomPerfts: List[Perft] = scala.compiletime.uninitialized
|
||||
var trickyPerfts: List[Perft] = scala.compiletime.uninitialized
|
||||
|
||||
@Setup
|
||||
def setup(): Unit =
|
||||
|
||||
@@ -19,8 +19,8 @@ class PgnBench:
|
||||
// the unit of CPU work per iteration
|
||||
private val Work: Long = 10
|
||||
|
||||
var pgnStrs: List[PgnStr] = scala.compiletime.uninitialized
|
||||
var pgns: List[Pgn] = scala.compiletime.uninitialized
|
||||
var pgnStrs: List[PgnStr] = scala.compiletime.uninitialized
|
||||
var pgns: List[Pgn] = scala.compiletime.uninitialized
|
||||
var parsedPgn: List[ParsedPgn] = scala.compiletime.uninitialized
|
||||
|
||||
@Setup
|
||||
@@ -32,7 +32,7 @@ class PgnBench:
|
||||
@Benchmark
|
||||
def pgnFullParser(bh: Blackhole) =
|
||||
var games = this.pgnStrs
|
||||
var i = 0
|
||||
var i = 0
|
||||
while i < games.size do
|
||||
val game = games(i)
|
||||
Blackhole.consumeCPU(Work)
|
||||
@@ -42,7 +42,7 @@ class PgnBench:
|
||||
@Benchmark
|
||||
def pgnMainlineWithMetasParser(bh: Blackhole) =
|
||||
var games = this.pgnStrs
|
||||
var i = 0
|
||||
var i = 0
|
||||
while i < games.size do
|
||||
val game = games(i)
|
||||
Blackhole.consumeCPU(Work)
|
||||
@@ -52,7 +52,7 @@ class PgnBench:
|
||||
@Benchmark
|
||||
def pgnMainlineParser(bh: Blackhole) =
|
||||
var games = this.pgnStrs
|
||||
var i = 0
|
||||
var i = 0
|
||||
while i < games.size do
|
||||
val game = games(i)
|
||||
Blackhole.consumeCPU(Work)
|
||||
|
||||
@@ -23,7 +23,7 @@ class PlayBench:
|
||||
private val Work: Long = 10
|
||||
|
||||
var dividerGames: List[List[Board]] = scala.compiletime.uninitialized
|
||||
var gameMoves: List[List[SanStr]] = scala.compiletime.uninitialized
|
||||
var gameMoves: List[List[SanStr]] = scala.compiletime.uninitialized
|
||||
|
||||
def gameReplay(sans: String) =
|
||||
Standard.initialPosition.playBoards(SanStr.from(sans.split(' ')).toList).toOption.get
|
||||
@@ -32,7 +32,7 @@ class PlayBench:
|
||||
def setup() =
|
||||
dividerGames = Fixtures.prod500standard.map(gameReplay)
|
||||
|
||||
var nb = 50
|
||||
var nb = 50
|
||||
var games = Fixtures.prod500standard
|
||||
gameMoves = games.take(nb).map(g => SanStr.from(g.split(' ').toList))
|
||||
|
||||
@@ -47,7 +47,7 @@ class PlayBench:
|
||||
@Benchmark
|
||||
def playMoveOrDropWithPly(bh: Blackhole) =
|
||||
val games = this.gameMoves
|
||||
var i = 0
|
||||
var i = 0
|
||||
while i < games.size do
|
||||
val moves = games(i)
|
||||
Blackhole.consumeCPU(Work)
|
||||
|
||||
@@ -7,41 +7,41 @@ import bitboard.Attacks.*
|
||||
opaque type Bitboard = Long
|
||||
object Bitboard:
|
||||
|
||||
inline def apply(inline l: Long): Bitboard = l
|
||||
inline def apply(inline l: Long): Bitboard = l
|
||||
inline def apply(inline xs: Iterable[Square]): Bitboard = xs.foldLeft(empty)((b, s) => b | s.bl)
|
||||
inline def apply(inline xs: Square*): Bitboard = apply(xs.toList)
|
||||
inline def apply(inline xs: Square*): Bitboard = apply(xs.toList)
|
||||
|
||||
val empty: Bitboard = 0L
|
||||
val all: Bitboard = -1L
|
||||
val all: Bitboard = -1L
|
||||
// E4, D4, E5, D5
|
||||
val center = 0x1818000000L
|
||||
|
||||
val firstRank: Bitboard = 0xffL
|
||||
val lastRank: Bitboard = 0xffL << 56
|
||||
val lastRank: Bitboard = 0xffL << 56
|
||||
|
||||
// all light squares
|
||||
val lightSquares: Bitboard = 0x55aa55aa55aa55aaL
|
||||
// all dark squares
|
||||
val darkSquares: Bitboard = 0xaa55aa55aa55aa55L
|
||||
|
||||
inline def file(inline f: File): Bitboard = FILES(f.value)
|
||||
inline def rank(inline r: Rank): Bitboard = RANKS(r.value)
|
||||
inline def file(inline f: File): Bitboard = FILES(f.value)
|
||||
inline def rank(inline r: Rank): Bitboard = RANKS(r.value)
|
||||
inline def ray(inline from: Square, inline to: Square): Bitboard = RAYS(from.value)(to.value)
|
||||
|
||||
def aligned(a: Square, b: Square, c: Square): Boolean = ray(a, b).contains(c)
|
||||
def between(a: Square, b: Square): Bitboard = BETWEEN(a.value)(b.value)
|
||||
def between(a: Square, b: Square): Bitboard = BETWEEN(a.value)(b.value)
|
||||
|
||||
extension (l: Long)
|
||||
private def lsb: Square = Square.unsafe(java.lang.Long.numberOfTrailingZeros(l))
|
||||
private def msb: Square = Square.unsafe(63 - java.lang.Long.numberOfLeadingZeros(l))
|
||||
|
||||
extension (a: Bitboard)
|
||||
inline def value: Long = a
|
||||
inline def unary_~ : Bitboard = (~a)
|
||||
inline infix def &(inline o: Long): Bitboard = (a & o)
|
||||
inline infix def ^(inline o: Long): Bitboard = (a ^ o)
|
||||
inline infix def |(inline o: Long): Bitboard = (a | o)
|
||||
inline infix def <<(inline o: Int): Bitboard = (a << o)
|
||||
inline def value: Long = a
|
||||
inline def unary_~ : Bitboard = (~a)
|
||||
inline infix def &(inline o: Long): Bitboard = (a & o)
|
||||
inline infix def ^(inline o: Long): Bitboard = (a ^ o)
|
||||
inline infix def |(inline o: Long): Bitboard = (a | o)
|
||||
inline infix def <<(inline o: Int): Bitboard = (a << o)
|
||||
inline infix def >>>(inline o: Int): Bitboard = (a >>> o)
|
||||
@targetName("and")
|
||||
inline infix def &(o: Bitboard): Bitboard = (a & o)
|
||||
@@ -50,7 +50,7 @@ object Bitboard:
|
||||
@targetName("or")
|
||||
inline infix def |(o: Bitboard): Bitboard = (a | o)
|
||||
|
||||
inline def isEmpty: Boolean = a == 0L
|
||||
inline def isEmpty: Boolean = a == 0L
|
||||
inline def nonEmpty: Boolean = !isEmpty
|
||||
|
||||
inline def supersetOf(l: Long): Boolean =
|
||||
@@ -73,7 +73,7 @@ object Bitboard:
|
||||
inline def contains(file: File, rank: Rank): Boolean =
|
||||
(a & file.bb & rank.bb) != 0L
|
||||
|
||||
def add(square: Square): Bitboard = a | square.bl
|
||||
def add(square: Square): Bitboard = a | square.bl
|
||||
def remove(square: Square): Bitboard = a & ~square.bl
|
||||
|
||||
def move(from: Square, to: Square): Bitboard =
|
||||
@@ -122,7 +122,7 @@ object Bitboard:
|
||||
|
||||
// return list of square that sorted ascendingly
|
||||
def squares: List[Square] =
|
||||
var b = a
|
||||
var b = a
|
||||
val builder = List.newBuilder[Square]
|
||||
while b != 0L
|
||||
do
|
||||
@@ -134,7 +134,7 @@ object Bitboard:
|
||||
squares.toSet
|
||||
|
||||
def first[B](f: Square => Option[B]): Option[B] =
|
||||
var b = a
|
||||
var b = a
|
||||
var result: Option[B] = None
|
||||
while b != 0L && result.isEmpty
|
||||
do
|
||||
@@ -143,7 +143,7 @@ object Bitboard:
|
||||
result
|
||||
|
||||
def last[B](f: Square => Option[B]): Option[B] =
|
||||
var b = a
|
||||
var b = a
|
||||
var result: Option[B] = None
|
||||
while b != 0L && result.isEmpty
|
||||
do
|
||||
@@ -153,7 +153,7 @@ object Bitboard:
|
||||
|
||||
// the smallest square that satisfies the predicate
|
||||
def find(f: Square => Boolean): Option[Square] =
|
||||
var b = a
|
||||
var b = a
|
||||
var result: Option[Square] = None
|
||||
while b != 0L && result.isEmpty
|
||||
do
|
||||
@@ -163,7 +163,7 @@ object Bitboard:
|
||||
|
||||
// the larget square that satisfies the predicate
|
||||
def findLast(f: Square => Boolean): Option[Square] =
|
||||
var b = a
|
||||
var b = a
|
||||
var result: Option[Square] = None
|
||||
while b != 0L && result.isEmpty
|
||||
do
|
||||
@@ -172,7 +172,7 @@ object Bitboard:
|
||||
result
|
||||
|
||||
def fold[B](init: B)(f: (B, Square) => B): B =
|
||||
var b = a
|
||||
var b = a
|
||||
var result = init
|
||||
while b != 0L
|
||||
do
|
||||
@@ -182,7 +182,7 @@ object Bitboard:
|
||||
|
||||
def filter(f: Square => Boolean): List[Square] =
|
||||
val builder = List.newBuilder[Square]
|
||||
var b = a
|
||||
var b = a
|
||||
while b != 0L
|
||||
do
|
||||
if f(b.lsb) then builder += b.lsb
|
||||
@@ -200,7 +200,7 @@ object Bitboard:
|
||||
b &= (b - 1L)
|
||||
|
||||
def forall(f: Square => Boolean): Boolean =
|
||||
var b = a
|
||||
var b = a
|
||||
var result = true
|
||||
while b != 0L && result
|
||||
do
|
||||
@@ -209,7 +209,7 @@ object Bitboard:
|
||||
result
|
||||
|
||||
def exists(f: Square => Boolean): Boolean =
|
||||
var b = a
|
||||
var b = a
|
||||
var result = false
|
||||
while b != 0L && !result
|
||||
do
|
||||
@@ -218,7 +218,7 @@ object Bitboard:
|
||||
result
|
||||
|
||||
def flatMap[B](f: Square => IterableOnce[B]): List[B] =
|
||||
var b = a
|
||||
var b = a
|
||||
val builder = List.newBuilder[B]
|
||||
while b != 0L
|
||||
do
|
||||
@@ -227,7 +227,7 @@ object Bitboard:
|
||||
builder.result
|
||||
|
||||
def map[B](f: Square => B): List[B] =
|
||||
var b = a
|
||||
var b = a
|
||||
val builder = List.newBuilder[B]
|
||||
while b != 0L
|
||||
do
|
||||
@@ -236,9 +236,9 @@ object Bitboard:
|
||||
builder.result
|
||||
|
||||
def iterator: Iterator[Square] = new:
|
||||
private var b = a
|
||||
private var b = a
|
||||
override inline def hasNext: Boolean = b != 0L
|
||||
override inline def next: Square =
|
||||
override inline def next: Square =
|
||||
val result = b.lsb
|
||||
b &= (b - 1L)
|
||||
result
|
||||
|
||||
@@ -310,26 +310,26 @@ object Board:
|
||||
)
|
||||
)
|
||||
def fromMap(pieces: PieceMap): Board =
|
||||
var pawns = Bitboard.empty
|
||||
var knights = Bitboard.empty
|
||||
var bishops = Bitboard.empty
|
||||
var rooks = Bitboard.empty
|
||||
var queens = Bitboard.empty
|
||||
var kings = Bitboard.empty
|
||||
var white = Bitboard.empty
|
||||
var black = Bitboard.empty
|
||||
var pawns = Bitboard.empty
|
||||
var knights = Bitboard.empty
|
||||
var bishops = Bitboard.empty
|
||||
var rooks = Bitboard.empty
|
||||
var queens = Bitboard.empty
|
||||
var kings = Bitboard.empty
|
||||
var white = Bitboard.empty
|
||||
var black = Bitboard.empty
|
||||
var occupied = Bitboard.empty
|
||||
|
||||
pieces.foreach: (s, p) =>
|
||||
val position = s.bb
|
||||
occupied |= position
|
||||
p.role match
|
||||
case Pawn => pawns |= position
|
||||
case Pawn => pawns |= position
|
||||
case Knight => knights |= position
|
||||
case Bishop => bishops |= position
|
||||
case Rook => rooks |= position
|
||||
case Queen => queens |= position
|
||||
case King => kings |= position
|
||||
case Rook => rooks |= position
|
||||
case Queen => queens |= position
|
||||
case King => kings |= position
|
||||
|
||||
p.color match
|
||||
case Color.White => white |= position
|
||||
|
||||
@@ -23,8 +23,8 @@ case class ByColor[A](white: A, black: A):
|
||||
|
||||
def map[B](fw: A => B, fb: A => B): ByColor[B] = ByColor(fw(white), fb(black))
|
||||
|
||||
def map[B](f: A => B): ByColor[B] = map(f, f)
|
||||
def mapList[B](f: A => B): List[B] = List(f(white), f(black))
|
||||
def map[B](f: A => B): ByColor[B] = map(f, f)
|
||||
def mapList[B](f: A => B): List[B] = List(f(white), f(black))
|
||||
def mapReduce[B, C](f: A => B)(r: (B, B) => C): C = r(f(white), f(black))
|
||||
|
||||
def mapWithColor[B](f: (Color, A) => B): ByColor[B] = ByColor(f(White, white), f(Black, black))
|
||||
@@ -33,13 +33,13 @@ case class ByColor[A](white: A, black: A):
|
||||
def zip[B, C](other: ByColor[B], f: (A, B) => C): ByColor[C] =
|
||||
ByColor(f(white, other.white), f(black, other.black))
|
||||
def zipColor: ByColor[(Color, A)] = ByColor((White, white), (Black, black))
|
||||
def toPair: (A, A) = (white, black)
|
||||
def toPair: (A, A) = (white, black)
|
||||
|
||||
lazy val all: List[A] = List(white, black)
|
||||
|
||||
def reduce[B](f: (A, A) => B): B = f(white, black)
|
||||
|
||||
def fold[B](init: B)(f: (B, A) => B): B = f(f(init, white), black)
|
||||
def fold[B](init: B)(f: (B, A) => B): B = f(f(init, white), black)
|
||||
def fold[B](init: B)(f: (B, Color, A) => B): B = f(f(init, White, white), Black, black)
|
||||
|
||||
def foreach[U](f: A => U): Unit =
|
||||
@@ -95,7 +95,7 @@ case class ByColor[A](white: A, black: A):
|
||||
(f(white), f(black)).mapN(r)
|
||||
|
||||
object ByColor:
|
||||
inline def fill[A](a: A): ByColor[A] = ByColor(a, a)
|
||||
inline def fill[A](a: A): ByColor[A] = ByColor(a, a)
|
||||
inline def fromPair[A](p: (A, A)): ByColor[A] = ByColor(p._1, p._2)
|
||||
|
||||
def apply[A](f: Color => A)(using NotGiven[f.type <:< PartialFunction[Color, A]]): ByColor[A] =
|
||||
@@ -112,7 +112,7 @@ object ByColor:
|
||||
def zero = ByColor.fill(Zero[A].zero)
|
||||
|
||||
given [A: Monoid]: Monoid[ByColor[A]] with
|
||||
def empty = ByColor.fill(Monoid[A].empty)
|
||||
def empty = ByColor.fill(Monoid[A].empty)
|
||||
def combine(x: ByColor[A], y: ByColor[A]) =
|
||||
ByColor(Monoid[A].combine(x.white, y.white), Monoid[A].combine(x.black, y.black))
|
||||
|
||||
@@ -129,7 +129,7 @@ object ByColor:
|
||||
def traverse[G[_]: Applicative, A, B](fa: ByColor[A])(f: A => G[B]): G[ByColor[B]] =
|
||||
fa.traverse(f)
|
||||
|
||||
def pure[A](a: A): ByColor[A] = ByColor.fill(a)
|
||||
def pure[A](a: A): ByColor[A] = ByColor.fill(a)
|
||||
def ap[A, B](ff: ByColor[A => B])(fa: ByColor[A]): ByColor[B] =
|
||||
ByColor(ff.white(fa.white), ff.black(fa.black))
|
||||
|
||||
|
||||
@@ -4,20 +4,20 @@ import cats.Functor
|
||||
|
||||
case class ByRole[A](pawn: A, knight: A, bishop: A, rook: A, queen: A, king: A):
|
||||
def apply(role: Role): A = role match
|
||||
case Pawn => pawn
|
||||
case Pawn => pawn
|
||||
case Knight => knight
|
||||
case Bishop => bishop
|
||||
case Rook => rook
|
||||
case Queen => queen
|
||||
case King => king
|
||||
case Rook => rook
|
||||
case Queen => queen
|
||||
case King => king
|
||||
|
||||
inline def update(role: Role, f: A => A): ByRole[A] = role match
|
||||
case Pawn => copy(pawn = f(pawn))
|
||||
case Pawn => copy(pawn = f(pawn))
|
||||
case Knight => copy(knight = f(knight))
|
||||
case Bishop => copy(bishop = f(bishop))
|
||||
case Rook => copy(rook = f(rook))
|
||||
case Queen => copy(queen = f(queen))
|
||||
case King => copy(king = f(king))
|
||||
case Rook => copy(rook = f(rook))
|
||||
case Queen => copy(queen = f(queen))
|
||||
case King => copy(king = f(king))
|
||||
|
||||
inline def find(f: A => Boolean): Option[A] =
|
||||
if f(pawn) then Some(pawn)
|
||||
|
||||
@@ -12,7 +12,7 @@ import scala.annotation.targetName
|
||||
* a typeclass that can apply a Moveable and produce a new state and a MoveOrDrop.
|
||||
*/
|
||||
trait CanPlay[A]:
|
||||
type Step = (next: A, move: MoveOrDrop, ply: Ply)
|
||||
type Step = (next: A, move: MoveOrDrop, ply: Ply)
|
||||
type Result[B] = (state: A, moves: List[B], error: Option[ErrorStr])
|
||||
extension (a: A)
|
||||
/**
|
||||
@@ -286,6 +286,6 @@ trait CanPlay[A]:
|
||||
object CanPlay:
|
||||
|
||||
inline def makeError(currentPly: Ply, move: Moveable): ErrorStr =
|
||||
val moveAt = currentPly.fullMoveNumber
|
||||
val moveAt = currentPly.fullMoveNumber
|
||||
val moveStr = move.rawString.getOrElse(move.toString)
|
||||
ErrorStr(s"Cannot play $moveStr at move $moveAt by ${currentPly.turn.name}")
|
||||
|
||||
@@ -9,14 +9,14 @@ object Castles:
|
||||
|
||||
extension (c: Castles)
|
||||
|
||||
inline def can(inline color: Color): Boolean = Bitboard.rank(color.backRank).intersects(c)
|
||||
inline def can(inline color: Color): Boolean = Bitboard.rank(color.backRank).intersects(c)
|
||||
inline def can(inline color: Color, inline side: Side) = c.contains(color.at(side))
|
||||
|
||||
def isEmpty = c == 0L
|
||||
|
||||
def whiteKingSide: Boolean = c.contains(H1)
|
||||
def whiteKingSide: Boolean = c.contains(H1)
|
||||
def whiteQueenSide: Boolean = c.contains(A1)
|
||||
def blackKingSide: Boolean = c.contains(H8)
|
||||
def blackKingSide: Boolean = c.contains(H8)
|
||||
def blackQueenSide: Boolean = c.contains(A8)
|
||||
|
||||
def without(color: Color): Castles =
|
||||
@@ -33,7 +33,7 @@ object Castles:
|
||||
|
||||
def toSeq: Array[Boolean] = Array(whiteKingSide, whiteQueenSide, blackKingSide, blackQueenSide)
|
||||
|
||||
inline def unary_~ : Castles = ~c
|
||||
inline def unary_~ : Castles = ~c
|
||||
inline infix def &(inline o: Long): Castles = c & o
|
||||
inline infix def ^(inline o: Long): Castles = c ^ o
|
||||
inline infix def |(inline o: Long): Castles = c | o
|
||||
@@ -45,7 +45,7 @@ object Castles:
|
||||
@targetName("orB")
|
||||
inline infix def |(o: Bitboard): Castles = c | o.value
|
||||
|
||||
inline def value: Long = c
|
||||
inline def value: Long = c
|
||||
inline def bb: Bitboard = Bitboard(c)
|
||||
|
||||
inline def contains(inline square: Square): Boolean =
|
||||
@@ -58,12 +58,12 @@ object Castles:
|
||||
extension (color: Color)
|
||||
inline def at(side: Side): Square =
|
||||
(color, side) match
|
||||
case (White, KingSide) => H1
|
||||
case (White, KingSide) => H1
|
||||
case (White, QueenSide) => A1
|
||||
case (Black, KingSide) => H8
|
||||
case (Black, KingSide) => H8
|
||||
case (Black, QueenSide) => A8
|
||||
|
||||
inline def kingSide: Square = at(KingSide)
|
||||
inline def kingSide: Square = at(KingSide)
|
||||
inline def queenSide: Square = at(QueenSide)
|
||||
|
||||
def apply(
|
||||
@@ -86,8 +86,8 @@ object Castles:
|
||||
case 'q' => Some(A8)
|
||||
case 'K' => Some(H1)
|
||||
case 'Q' => Some(A1)
|
||||
case _ => None
|
||||
case _ => None
|
||||
|
||||
val init: Castles = 0x8100000000000081L
|
||||
val none: Castles = 0L
|
||||
val init: Castles = 0x8100000000000081L
|
||||
val none: Castles = 0L
|
||||
val black: Castles = 0x8100000000000000L
|
||||
|
||||
@@ -16,14 +16,14 @@ object Centis extends RichOpaqueInt[Centis]:
|
||||
|
||||
inline def *(inline o: Int): Centis = centis * o
|
||||
|
||||
def roundTenths: Int = (if centis > 0 then centis + 5 else centis - 4) / 10
|
||||
def roundTenths: Int = (if centis > 0 then centis + 5 else centis - 4) / 10
|
||||
def roundSeconds: Seconds = Seconds(Math.round(centis * 0.01f))
|
||||
|
||||
inline def toSeconds: BigDecimal = java.math.BigDecimal.valueOf(centis, 2)
|
||||
inline def millis: Long = centis * 10L
|
||||
def toDuration: FiniteDuration = FiniteDuration(millis, MILLISECONDS)
|
||||
inline def millis: Long = centis * 10L
|
||||
def toDuration: FiniteDuration = FiniteDuration(millis, MILLISECONDS)
|
||||
|
||||
def *~(scalar: Float): Centis = ofFloat(scalar * centis)
|
||||
def *~(scalar: Float): Centis = ofFloat(scalar * centis)
|
||||
def /(div: Int): Option[Centis] = (div != 0).option(centis / div)
|
||||
|
||||
def avg(other: Centis): Centis = (centis + other.value) >> 1
|
||||
@@ -36,7 +36,7 @@ object Centis extends RichOpaqueInt[Centis]:
|
||||
|
||||
given Monoid[Centis] with
|
||||
def combine(c1: Centis, c2: Centis) = c1 + c2
|
||||
val empty = 0
|
||||
val empty = 0
|
||||
|
||||
def ofLong(l: Long): Centis =
|
||||
try Math.toIntExact(l)
|
||||
@@ -50,7 +50,7 @@ object Centis extends RichOpaqueInt[Centis]:
|
||||
if d.unit eq MILLISECONDS then d.length
|
||||
else d.toMillis
|
||||
|
||||
inline def ofFloat(f: Float): Centis = Math.round(f)
|
||||
inline def ofFloat(f: Float): Centis = Math.round(f)
|
||||
inline def ofDouble(d: Double): Centis = ofLong(Math.round(d))
|
||||
|
||||
inline def ofSeconds(s: Int): Centis = 100 * s
|
||||
|
||||
@@ -80,15 +80,15 @@ case class Clock(
|
||||
)
|
||||
case Some(t) =>
|
||||
val elapsed = toNow(t)
|
||||
val lag = (~metrics.reportedLag(elapsed)).nonNeg
|
||||
val lag = (~metrics.reportedLag(elapsed)).nonNeg
|
||||
|
||||
val player = players(color)
|
||||
val player = players(color)
|
||||
val (lagComp, lagTrack) = player.lag.onMove(lag)
|
||||
|
||||
val moveTime = (elapsed - lagComp).nonNeg
|
||||
|
||||
val clockActive = gameActive && moveTime < player.remaining
|
||||
val inc = clockActive.so(player.increment)
|
||||
val inc = clockActive.so(player.increment)
|
||||
|
||||
val newC = updatePlayer(color):
|
||||
_.takeTime(moveTime - inc)
|
||||
@@ -117,7 +117,7 @@ case class Clock(
|
||||
def goBerserk(c: Color): Clock = updatePlayer(c)(_.copy(berserk = true))
|
||||
|
||||
def berserked(c: Color): Boolean = players(c).berserk
|
||||
def lag(c: Color): LagTracker = players(c).lag
|
||||
def lag(c: Color): LagTracker = players(c).lag
|
||||
|
||||
def lagCompAvg: Centis = players.mapReduce(~_.lag.compAvg)(_.avg(_))
|
||||
|
||||
@@ -192,7 +192,7 @@ object Clock:
|
||||
case 30 => "½"
|
||||
case 45 => "¾"
|
||||
case 90 => "1.5"
|
||||
case _ => limitFormatter.format(limitSeconds / 60d)
|
||||
case _ => limitFormatter.format(limitSeconds / 60d)
|
||||
|
||||
def show: String = toString
|
||||
|
||||
|
||||
@@ -18,24 +18,24 @@ enum Color(val name: String, val letter: Char) derives Eq:
|
||||
@targetName("negate")
|
||||
def unary_! = fold(Black, White)
|
||||
|
||||
lazy val backRank: Rank = fold(Rank.First, Rank.Eighth)
|
||||
lazy val thirdRank: Rank = fold(Rank.Third, Rank.Sixth)
|
||||
lazy val fourthRank: Rank = fold(Rank.Fourth, Rank.Fifth)
|
||||
lazy val fifthRank: Rank = fold(Rank.Fifth, Rank.Fourth)
|
||||
lazy val sixthRank: Rank = fold(Rank.Sixth, Rank.Third)
|
||||
lazy val seventhRank: Rank = fold(Rank.Seventh, Rank.Second)
|
||||
lazy val lastRank: Rank = fold(Rank.Eighth, Rank.First)
|
||||
lazy val passablePawnRank: Rank = fifthRank
|
||||
lazy val backRank: Rank = fold(Rank.First, Rank.Eighth)
|
||||
lazy val thirdRank: Rank = fold(Rank.Third, Rank.Sixth)
|
||||
lazy val fourthRank: Rank = fold(Rank.Fourth, Rank.Fifth)
|
||||
lazy val fifthRank: Rank = fold(Rank.Fifth, Rank.Fourth)
|
||||
lazy val sixthRank: Rank = fold(Rank.Sixth, Rank.Third)
|
||||
lazy val seventhRank: Rank = fold(Rank.Seventh, Rank.Second)
|
||||
lazy val lastRank: Rank = fold(Rank.Eighth, Rank.First)
|
||||
lazy val passablePawnRank: Rank = fifthRank
|
||||
lazy val promotablePawnRank: Rank = lastRank
|
||||
|
||||
inline def -(inline role: Role) = Piece(this, role)
|
||||
|
||||
inline def pawn = this - Pawn
|
||||
inline def pawn = this - Pawn
|
||||
inline def bishop = this - Bishop
|
||||
inline def knight = this - Knight
|
||||
inline def rook = this - Rook
|
||||
inline def queen = this - Queen
|
||||
inline def king = this - King
|
||||
inline def rook = this - Rook
|
||||
inline def queen = this - Queen
|
||||
inline def king = this - King
|
||||
|
||||
override def hashCode = fold(1, 2)
|
||||
|
||||
|
||||
@@ -33,10 +33,10 @@ case class CorrespondenceClock(
|
||||
|
||||
object CorrespondenceClock:
|
||||
val hourSeconds = 60 * 60
|
||||
val daySeconds = 24 * hourSeconds
|
||||
val daySeconds = 24 * hourSeconds
|
||||
|
||||
def apply(daysPerTurn: Int, turnColor: Color, lastMoveAt: Instant): CorrespondenceClock =
|
||||
val increment = daysPerTurn * 24 * 60 * 60
|
||||
val increment = daysPerTurn * 24 * 60 * 60
|
||||
val secondsLeft = (lastMoveAt.toSeconds + increment - nowSeconds).toInt.max(0)
|
||||
CorrespondenceClock(
|
||||
increment = increment,
|
||||
|
||||
@@ -6,12 +6,12 @@ import scala.annotation.switch
|
||||
|
||||
case class Division(middle: Option[Ply], end: Option[Ply], plies: Ply):
|
||||
|
||||
def openingSize: Ply = middle | plies
|
||||
def middleSize: Option[Ply] = middle.map((end | plies) - _)
|
||||
def endSize: Option[Ply] = end.map(plies - _)
|
||||
def openingSize: Ply = middle | plies
|
||||
def middleSize: Option[Ply] = middle.map((end | plies) - _)
|
||||
def endSize: Option[Ply] = end.map(plies - _)
|
||||
def openingBounds: Option[(Int, Ply)] = middle.map(0 -> _)
|
||||
def middleBounds: Option[(Ply, Ply)] = (middle, end).tupled
|
||||
def endBounds: Option[(Ply, Ply)] = end.map(_ -> plies)
|
||||
def middleBounds: Option[(Ply, Ply)] = (middle, end).tupled
|
||||
def endBounds: Option[(Ply, Ply)] = end.map(_ -> plies)
|
||||
|
||||
object Division:
|
||||
val empty = Division(None, None, Ply.initial)
|
||||
|
||||
@@ -6,14 +6,14 @@ object File:
|
||||
extension (a: File)
|
||||
inline def value: Int = a
|
||||
|
||||
inline infix def >(inline o: File): Boolean = a > o
|
||||
inline infix def <(inline o: File): Boolean = a < o
|
||||
inline infix def >(inline o: File): Boolean = a > o
|
||||
inline infix def <(inline o: File): Boolean = a < o
|
||||
inline infix def >=(inline o: File): Boolean = a >= o
|
||||
inline infix def <=(inline o: File): Boolean = a <= o
|
||||
|
||||
inline def char: Char = (97 + a).toChar
|
||||
|
||||
inline def upperCaseChar: Char = (65 + a).toChar
|
||||
inline def upperCaseChar: Char = (65 + a).toChar
|
||||
inline def toUpperCaseString: String = upperCaseChar.toString
|
||||
|
||||
// the bitboard of the file
|
||||
|
||||
@@ -45,7 +45,7 @@ case class Game(
|
||||
|
||||
def applyWithCompensated(move: Move): Clock.WithCompensatedLag[Game] =
|
||||
val newPosition = move.after
|
||||
val newClock = applyClock(move.metrics, newPosition.status.isEmpty)
|
||||
val newClock = applyClock(move.metrics, newPosition.status.isEmpty)
|
||||
|
||||
Clock.WithCompensatedLag(
|
||||
copy(
|
||||
@@ -75,8 +75,8 @@ case class Game(
|
||||
val c2 = c1.step(metrics, gameActive)
|
||||
if ply - startedAtPly == Ply(1) then c2.map(_.start) else c2
|
||||
|
||||
def apply(uci: Uci.Move): Either[ErrorStr, (Game, Move)] = apply(uci.orig, uci.dest, uci.promotion)
|
||||
def apply(uci: Uci.Drop): Either[ErrorStr, (Game, Drop)] = drop(uci.role, uci.square)
|
||||
def apply(uci: Uci.Move): Either[ErrorStr, (Game, Move)] = apply(uci.orig, uci.dest, uci.promotion)
|
||||
def apply(uci: Uci.Drop): Either[ErrorStr, (Game, Drop)] = drop(uci.role, uci.square)
|
||||
def apply(uci: Uci): Either[ErrorStr, (Game, MoveOrDrop)] =
|
||||
uci match
|
||||
case uci: Uci.Move => apply(uci)
|
||||
|
||||
@@ -6,7 +6,7 @@ trait HasId[A, Id]:
|
||||
extension (a: A)
|
||||
def id: Id
|
||||
inline def sameId(other: A): Boolean = a.id == other.id
|
||||
inline def hasId(id: Id): Boolean = a.id == id
|
||||
inline def hasId(id: Id): Boolean = a.id == id
|
||||
|
||||
extension (xs: List[A])
|
||||
final def remove(v: A): List[A] =
|
||||
@@ -21,8 +21,8 @@ trait HasId[A, Id]:
|
||||
def loop(acc: List[A], xs: List[A]): List[A] =
|
||||
xs match
|
||||
case (v :: vs) if v.hasId(id) => acc ++ vs
|
||||
case (v :: vs) => loop(acc :+ v, vs)
|
||||
case Nil => acc
|
||||
case (v :: vs) => loop(acc :+ v, vs)
|
||||
case Nil => acc
|
||||
loop(Nil, xs)
|
||||
|
||||
trait Mergeable[A]:
|
||||
@@ -48,11 +48,11 @@ trait Mergeable[A]:
|
||||
@tailrec
|
||||
def loop(acc: List[A], rest: List[A]): List[A] =
|
||||
rest match
|
||||
case Nil => acc :+ v
|
||||
case Nil => acc :+ v
|
||||
case y :: ys =>
|
||||
y.merge(v) match
|
||||
case Some(m) => acc ++ (m +: ys)
|
||||
case _ => loop(acc :+ y, ys)
|
||||
case _ => loop(acc :+ y, ys)
|
||||
|
||||
loop(Nil, xs)
|
||||
|
||||
|
||||
@@ -6,5 +6,5 @@ trait HasPosition[A]:
|
||||
extension (a: A)
|
||||
def position: Position
|
||||
inline def variant: Variant = position.variant
|
||||
inline def color: Color = position.color
|
||||
inline def color: Color = position.color
|
||||
inline def history: History = position.history
|
||||
|
||||
@@ -3,23 +3,23 @@ package chess
|
||||
opaque type PositionHash = Array[Byte]
|
||||
object PositionHash:
|
||||
def apply(value: Array[Byte]): PositionHash = value
|
||||
def apply(h: Hash): PositionHash = Array((h >>> 16).toByte, (h >>> 8).toByte, h.toByte)
|
||||
val empty: PositionHash = Array.empty
|
||||
def apply(h: Hash): PositionHash = Array((h >>> 16).toByte, (h >>> 8).toByte, h.toByte)
|
||||
val empty: PositionHash = Array.empty
|
||||
|
||||
extension (p: PositionHash)
|
||||
def value: Array[Byte] = p
|
||||
inline def isEmpty: Boolean = p.length == 0
|
||||
def value: Array[Byte] = p
|
||||
inline def isEmpty: Boolean = p.length == 0
|
||||
inline def combine(other: PositionHash): PositionHash = p ++ other
|
||||
def isRepetition(times: Int) =
|
||||
def isRepetition(times: Int) =
|
||||
if times <= 1 then true
|
||||
else if p.length <= (times - 1) * 4 * Hash.size then false
|
||||
else
|
||||
// compare only hashes for positions with the same side to move
|
||||
var i = Hash.size * 2
|
||||
var i = Hash.size * 2
|
||||
var count = 0
|
||||
val x = p(0)
|
||||
val y = p(1)
|
||||
val z = p(2)
|
||||
val x = p(0)
|
||||
val y = p(1)
|
||||
val z = p(2)
|
||||
while i <= p.length - Hash.size && count < times - 1
|
||||
do
|
||||
if x == p(i) && y == p(i + 1) && z == p(i + 2) then count += 1
|
||||
@@ -28,8 +28,8 @@ object PositionHash:
|
||||
|
||||
opaque type Hash = Int
|
||||
object Hash:
|
||||
val size = 3
|
||||
def apply(value: Int): Hash = value >>> 8
|
||||
val size = 3
|
||||
def apply(value: Int): Hash = value >>> 8
|
||||
def apply(position: Position): Hash = hashPosition(position) >>> 8
|
||||
|
||||
private def hashPosition(position: Position): Int =
|
||||
|
||||
@@ -17,9 +17,9 @@ case class History(
|
||||
def setHalfMoveClock(v: HalfMoveClock): History = copy(halfMoveClock = v)
|
||||
|
||||
inline def threefoldRepetition: Boolean = positionHashes.isRepetition(3)
|
||||
inline def fivefoldRepetition: Boolean = positionHashes.isRepetition(5)
|
||||
inline def fivefoldRepetition: Boolean = positionHashes.isRepetition(5)
|
||||
|
||||
inline def canCastle(inline color: Color): Boolean = castles.can(color)
|
||||
inline def canCastle(inline color: Color): Boolean = castles.can(color)
|
||||
inline def canCastle(inline color: Color, inline side: Side): Boolean = castles.can(color, side)
|
||||
|
||||
inline def withoutCastles(inline color: Color): History = copy(castles = castles.without(color))
|
||||
|
||||
@@ -52,7 +52,7 @@ object InsufficientMatingMaterial:
|
||||
def apply(board: Board, color: Color): Boolean =
|
||||
import board.*
|
||||
inline def onlyKing = kingsOnlyOf(color)
|
||||
inline def KN =
|
||||
inline def KN =
|
||||
onlyOf(color, King, Knight) && count(color, Knight) == 1 && onlyOf(!color, King, Queen)
|
||||
inline def KB =
|
||||
onlyOf(color, King, Bishop) &&
|
||||
|
||||
@@ -14,9 +14,9 @@ final case class LagTracker(
|
||||
):
|
||||
|
||||
def onMove(lag: Centis) =
|
||||
val comp = lag.atMost(quota)
|
||||
val comp = lag.atMost(quota)
|
||||
val uncomped = lag - comp
|
||||
val ceDiff = compEstimate.getOrElse(Centis(1)) - comp
|
||||
val ceDiff = compEstimate.getOrElse(Centis(1)) - comp
|
||||
|
||||
(
|
||||
comp,
|
||||
|
||||
@@ -66,7 +66,7 @@ case class Move(
|
||||
inline def withMetrics(m: MoveMetrics): Move = copy(metrics = m)
|
||||
|
||||
override lazy val toSanStr: SanStr = format.pgn.Dumper(this)
|
||||
override lazy val toUci: Uci.Move = Uci.Move(orig, dest, promotion)
|
||||
override lazy val toUci: Uci.Move = Uci.Move(orig, dest, promotion)
|
||||
|
||||
override def toString = s"$piece ${toUci.uci}"
|
||||
|
||||
@@ -92,13 +92,13 @@ case class Move(
|
||||
lazy val positionHashesOfBoardBefore =
|
||||
if h.positionHashes.isEmpty then PositionHash(Hash(before)) else h.positionHashes
|
||||
val resetsPositionHashes = after.variant.isIrreversible(this)
|
||||
val basePositionHashes =
|
||||
val basePositionHashes =
|
||||
if resetsPositionHashes then PositionHash.empty else positionHashesOfBoardBefore
|
||||
h.copy(positionHashes = PositionHash(Hash(after)).combine(basePositionHashes))
|
||||
}
|
||||
|
||||
private def castleRights: (Castles, UnmovedRooks) =
|
||||
var castleRights: Castles = afterWithoutHistory.history.castles
|
||||
var castleRights: Castles = afterWithoutHistory.history.castles
|
||||
var unmovedRooks: UnmovedRooks = afterWithoutHistory.history.unmovedRooks
|
||||
|
||||
// if the rook is captured
|
||||
@@ -144,7 +144,7 @@ end Move
|
||||
object Move:
|
||||
|
||||
case class Castle(king: Square, kingTo: Square, rook: Square, rookTo: Square):
|
||||
def side: Side = if kingTo.file == File.C then QueenSide else KingSide
|
||||
def side: Side = if kingTo.file == File.C then QueenSide else KingSide
|
||||
def isStandard: Boolean = king.file == File.E && (rook.file == File.A || rook.file == File.H)
|
||||
|
||||
case class Drop(
|
||||
@@ -159,9 +159,9 @@ case class Drop(
|
||||
|
||||
inline def withMetrics(m: MoveMetrics): Drop = copy(metrics = m)
|
||||
|
||||
override inline def color: Color = piece.color
|
||||
override inline def color: Color = piece.color
|
||||
override lazy val toSanStr: SanStr = format.pgn.Dumper(this)
|
||||
override lazy val toUci: Uci.Drop = Uci.Drop(piece.role, square)
|
||||
override lazy val toUci: Uci.Drop = Uci.Drop(piece.role, square)
|
||||
|
||||
override def toString = toUci.uci
|
||||
|
||||
|
||||
@@ -9,15 +9,15 @@ import scala.annotation.{ tailrec, targetName }
|
||||
sealed abstract class Tree[A](val value: A, val child: Option[Node[A]]) derives Traverse:
|
||||
|
||||
final def withValue(value: A): TreeSelector[A, this.type] = this match
|
||||
case n: Node[A] => n.copy(value = value)
|
||||
case n: Node[A] => n.copy(value = value)
|
||||
case v: Variation[A] => v.copy(value = value)
|
||||
|
||||
final def updateValue(f: A => A): TreeSelector[A, this.type] = this match
|
||||
case n: Node[A] => n.copy(value = f(value))
|
||||
case n: Node[A] => n.copy(value = f(value))
|
||||
case v: Variation[A] => v.copy(value = f(value))
|
||||
|
||||
final def withChild(child: Option[Node[A]]): TreeSelector[A, this.type] = this match
|
||||
case n: Node[A] => n.copy(child = child)
|
||||
case n: Node[A] => n.copy(child = child)
|
||||
case v: Variation[A] => v.copy(child = child)
|
||||
|
||||
final def updateChild(f: Option[Node[A]] => Option[Node[A]]): TreeSelector[A, this.type] =
|
||||
@@ -26,7 +26,7 @@ sealed abstract class Tree[A](val value: A, val child: Option[Node[A]]) derives
|
||||
final def withoutChild: TreeSelector[A, this.type] = withChild(none)
|
||||
|
||||
final def isNode: Boolean = this match
|
||||
case _: Node[A] => true
|
||||
case _: Node[A] => true
|
||||
case _: Variation[A] => false
|
||||
|
||||
final def isVariation: Boolean = !this.isNode
|
||||
@@ -45,13 +45,13 @@ sealed abstract class Tree[A](val value: A, val child: Option[Node[A]]) derives
|
||||
|
||||
def variations: List[Tree[A]] =
|
||||
this match
|
||||
case n: Node[A] => n.variations
|
||||
case n: Node[A] => n.variations
|
||||
case _: Variation[A] => Nil
|
||||
|
||||
final def mainlineValues: List[A] =
|
||||
@tailrec
|
||||
def loop(tree: Tree[A], acc: List[A]): List[A] = tree.child match
|
||||
case None => tree.value :: acc
|
||||
case None => tree.value :: acc
|
||||
case Some(child) => loop(child, tree.value :: acc)
|
||||
loop(this, Nil).reverse
|
||||
|
||||
@@ -60,23 +60,23 @@ sealed abstract class Tree[A](val value: A, val child: Option[Node[A]]) derives
|
||||
def loop(tree: Tree[A], acc: List[Id]): List[Id] =
|
||||
tree.child match
|
||||
case Some(child) => loop(child, tree.value.id +: acc)
|
||||
case None => tree.value.id +: acc
|
||||
case None => tree.value.id +: acc
|
||||
loop(this, Nil).reverse
|
||||
|
||||
final def findPathReserve[Id](path: List[Id])(using HasId[A, Id]): Option[List[Tree[A]]] =
|
||||
@tailrec
|
||||
def loop(tree: Tree[A], path: List[Id], acc: List[Tree[A]]): Option[List[Tree[A]]] = path match
|
||||
case Nil => None
|
||||
case head :: Nil if tree.hasId(head) => (tree :: acc).some
|
||||
case Nil => None
|
||||
case head :: Nil if tree.hasId(head) => (tree :: acc).some
|
||||
case head :: rest if tree.hasId(head) =>
|
||||
tree.child match
|
||||
case None => None
|
||||
case None => None
|
||||
case Some(child) => loop(child, rest, tree :: acc)
|
||||
case head :: _ =>
|
||||
tree match
|
||||
case node: Node[A] =>
|
||||
node.findVariation(head) match
|
||||
case None => None
|
||||
case None => None
|
||||
case Some(variation) => loop(variation, path, acc)
|
||||
case _ => None
|
||||
|
||||
@@ -136,10 +136,10 @@ sealed abstract class Tree[A](val value: A, val child: Option[Node[A]]) derives
|
||||
modifyAt(path, _.addChild(node).some)
|
||||
|
||||
type TreeSelector[A, X <: Tree[A]] <: Tree[A] = X match
|
||||
case Node[A] => Node[A]
|
||||
case Node[A] => Node[A]
|
||||
case Variation[A] => Variation[A]
|
||||
|
||||
type TreeMapper[A] = (tree: Tree[A]) => TreeSelector[A, tree.type]
|
||||
type TreeMapper[A] = (tree: Tree[A]) => TreeSelector[A, tree.type]
|
||||
type TreeMapOption[A] = (tree: Tree[A]) => Option[TreeSelector[A, tree.type]]
|
||||
|
||||
final case class Node[A](
|
||||
@@ -162,7 +162,7 @@ final case class Node[A](
|
||||
def loop(tree: Node[A], acc: List[Node[A]]): List[Node[A]] =
|
||||
tree.child match
|
||||
case Some(child) => loop(child, tree :: acc)
|
||||
case None => tree :: acc
|
||||
case None => tree :: acc
|
||||
loop(this, Nil)
|
||||
|
||||
/**
|
||||
@@ -176,7 +176,7 @@ final case class Node[A](
|
||||
if n <= 0 then acc
|
||||
else
|
||||
node.child match
|
||||
case None => node :: acc
|
||||
case None => node :: acc
|
||||
case Some(child) => loop(n - 1, child, node.withoutChild :: acc)
|
||||
Option.when(n > 0)(Tree.buildReverse(loop(n, this, Nil)).getOrElse(this))
|
||||
|
||||
@@ -190,7 +190,7 @@ final case class Node[A](
|
||||
if !f(node.value) then acc
|
||||
else
|
||||
node.child match
|
||||
case None => node :: acc
|
||||
case None => node :: acc
|
||||
case Some(child) => loop(child, node.withoutChild :: acc)
|
||||
Tree.buildReverse(loop(this, Nil))
|
||||
|
||||
@@ -204,68 +204,68 @@ final case class Node[A](
|
||||
|
||||
def modifyChildAt[Id](path: List[Id], f: Node[A] => Option[Node[A]]): HasId[A, Id] ?=> Option[Node[A]] =
|
||||
path match
|
||||
case Nil => f(this)
|
||||
case head :: Nil if this.hasId(head) => child.map(c => withChild(f(c)))
|
||||
case Nil => f(this)
|
||||
case head :: Nil if this.hasId(head) => child.map(c => withChild(f(c)))
|
||||
case head :: rest if this.hasId(head) =>
|
||||
child.flatMap(_.modifyChildAt(rest, f)) match
|
||||
case None => None
|
||||
case None => None
|
||||
case Some(c) => copy(child = c.some).some
|
||||
case _ =>
|
||||
variations.mapAccumulate(false):
|
||||
case (true, n) => (true, n)
|
||||
case (true, n) => (true, n)
|
||||
case (false, n) =>
|
||||
n.modifyChildAt(path, f) match
|
||||
case Some(nn) => (true, nn)
|
||||
case None => (false, n)
|
||||
case None => (false, n)
|
||||
match
|
||||
case (true, ns) => copy(variations = ns).some
|
||||
case (false, _) => none
|
||||
|
||||
def modifyAt[Id](path: List[Id], f: TreeMapOption[A]): HasId[A, Id] ?=> Option[Node[A]] =
|
||||
path match
|
||||
case Nil => None
|
||||
case head :: Nil if this.hasId(head) => f(this)
|
||||
case Nil => None
|
||||
case head :: Nil if this.hasId(head) => f(this)
|
||||
case head :: rest if this.hasId(head) =>
|
||||
child.flatMap(_.modifyAt(rest, f)) match
|
||||
case None => None
|
||||
case None => None
|
||||
case Some(c) => copy(child = c.some).some
|
||||
case head :: _ =>
|
||||
variations.mapAccumulate(false.some):
|
||||
case (Some(true), n) => (true.some, n)
|
||||
case (Some(true), n) => (true.some, n)
|
||||
case (Some(_), n) if n.hasId(head) =>
|
||||
n.modifyAt(path, f) match
|
||||
case None => (None, n)
|
||||
case None => (None, n)
|
||||
case Some(nn) => (true.some, nn)
|
||||
case (x, n) => (x, n)
|
||||
match
|
||||
case (Some(true), ns) => copy(variations = ns).some
|
||||
case _ => none
|
||||
case _ => none
|
||||
|
||||
// delete the node at the end of the path
|
||||
// return None if path is not found
|
||||
// return Some(None) if the node is deleted
|
||||
def deleteAt[Id](path: List[Id])(using HasId[A, Id]): Option[Option[Node[A]]] =
|
||||
path match
|
||||
case Nil => None
|
||||
case head :: Nil if this.hasId(head) => Some(None)
|
||||
case Nil => None
|
||||
case head :: Nil if this.hasId(head) => Some(None)
|
||||
case head :: rest if this.hasId(head) =>
|
||||
child.flatMap(_.deleteAt(rest)) match
|
||||
case None => None
|
||||
case None => None
|
||||
case Some(c) => copy(child = c).some.some
|
||||
case head :: _ =>
|
||||
variations.foldLeft((false.some, List.empty[Variation[A]])) {
|
||||
case ((Some(true), acc), n) => (true.some, n :: acc)
|
||||
case ((Some(true), acc), n) => (true.some, n :: acc)
|
||||
case ((Some(false), acc), n) if n.hasId(head) =>
|
||||
n.deleteAt(path) match
|
||||
case None => (None, n :: acc)
|
||||
case None => (None, n :: acc)
|
||||
case Some(nn) => (true.some, nn ++: acc)
|
||||
case ((x, acc), n) => (x, n :: acc)
|
||||
} match
|
||||
case (Some(true), ns) => copy(variations = ns.reverse).some.some
|
||||
case _ => none
|
||||
case _ => none
|
||||
|
||||
def promoteToMainline[Id](path: List[Id])(using HasId[A, Id]): Option[Node[A]] = path match
|
||||
case Nil => None
|
||||
case Nil => None
|
||||
case head :: Nil =>
|
||||
promote(head)
|
||||
case head :: rest =>
|
||||
@@ -276,14 +276,14 @@ final case class Node[A](
|
||||
// find the lastest variation in the path
|
||||
// promote it into mainline
|
||||
def promote[Id](path: List[Id])(using HasId[A, Id]): Option[Node[A]] = path match
|
||||
case Nil => None
|
||||
case Nil => None
|
||||
case head :: _ =>
|
||||
findPathReserve(path).flatMap: ps =>
|
||||
if ps.forall(_.isVariation) then this.promote(head)
|
||||
else if ps.forall(_.isNode) then None
|
||||
else
|
||||
ps.dropWhile(_.isNode).dropWhile(_.isVariation) match
|
||||
case Nil => this.promote(head)
|
||||
case Nil => this.promote(head)
|
||||
case rest =>
|
||||
val swappingNodePath = rest.map(_.id).reverse
|
||||
path
|
||||
@@ -297,7 +297,7 @@ final case class Node[A](
|
||||
if this.hasId(id) then this.some
|
||||
else
|
||||
variations.find(_.hasId(id)) match
|
||||
case None => None
|
||||
case None => None
|
||||
case Some(v) =>
|
||||
val vs = this.toVariations.removeById(id)
|
||||
v.toNode.withVariations(vs).some
|
||||
@@ -310,9 +310,9 @@ final case class Node[A](
|
||||
// because variations only use accumulated value from the parent
|
||||
def mapAccuml[S, B](init: S)(f: (S, A) => (S, B)): (S, Node[B]) =
|
||||
val (s1, b) = f(init, value)
|
||||
val v = variations.map(_.mapAccuml(init)(f)._2)
|
||||
val v = variations.map(_.mapAccuml(init)(f)._2)
|
||||
child.map(_.mapAccuml(s1)(f)) match
|
||||
case None => (s1, Node(b, None, v))
|
||||
case None => (s1, Node(b, None, v))
|
||||
case Some(s) => (s._1, Node(b, s._2.some, v))
|
||||
|
||||
def mapAccuml_[S, B](init: S)(f: (S, A) => (S, B)): Node[B] =
|
||||
@@ -325,11 +325,11 @@ final case class Node[A](
|
||||
// should we promote a variation to mainline if the f(value) is None?
|
||||
def mapAccumlOption[S, B](init: S)(f: (S, A) => (S, Option[B])): (S, Option[Node[B]]) =
|
||||
f(init, value) match
|
||||
case (s1, None) => (s1, None)
|
||||
case (s1, None) => (s1, None)
|
||||
case (s1, Some(b)) =>
|
||||
val vs = variations.map(_.mapAccumlOption(init)(f)._2).flatten
|
||||
child.map(_.mapAccumlOption(s1)(f)) match
|
||||
case None => (s1, Node(b, None, vs).some)
|
||||
case None => (s1, Node(b, None, vs).some)
|
||||
case Some(s) => (s._1, Node(b, s._2, vs).some)
|
||||
|
||||
def mapAccumlOption_[S, B](init: S)(f: (S, A) => (S, Option[B])): Option[Node[B]] =
|
||||
@@ -341,7 +341,7 @@ final case class Node[A](
|
||||
if predicate(value) then this.some
|
||||
else
|
||||
child.match
|
||||
case None => None
|
||||
case None => None
|
||||
case Some(c) => c.findInMainline(predicate)
|
||||
|
||||
def modifyInMainline(predicate: A => Boolean, f: Node[A] => Node[A]): Option[Node[A]] =
|
||||
@@ -368,13 +368,13 @@ final case class Node[A](
|
||||
else if n == 0 then this.some
|
||||
else
|
||||
child.match
|
||||
case None => none
|
||||
case None => none
|
||||
case Some(c) => c.getMainlineNodeAt(n - 1)
|
||||
|
||||
@tailrec
|
||||
def lastMainlineNode: Node[A] =
|
||||
child match
|
||||
case None => this
|
||||
case None => this
|
||||
case Some(c) => c.lastMainlineNode
|
||||
|
||||
def modifyLastMainlineNode(f: Node[A] => Node[A]): Node[A] =
|
||||
@@ -391,7 +391,7 @@ final case class Node[A](
|
||||
n.copy(value = f(n.value, i), child = n.child.map(c => loop(c, i + 1)), variations = Nil)
|
||||
loop(this, 0)
|
||||
|
||||
def toVariation: Variation[A] = Variation(value, child)
|
||||
def toVariation: Variation[A] = Variation(value, child)
|
||||
def toVariations: List[Variation[A]] = Variation(value, child) +: variations
|
||||
|
||||
// we assume that they have the same path from the roof
|
||||
@@ -404,7 +404,7 @@ final case class Node[A](
|
||||
def mergeOrAddAsVariation(v: Variation[A])(using Mergeable[A]): Node[A] =
|
||||
value.merge(v.value) match
|
||||
case Some(newValue) => Node(newValue, Tree.merge(child, v.child), variations)
|
||||
case _ => addVariation(v)
|
||||
case _ => addVariation(v)
|
||||
|
||||
def mergeOrAddAsVariation(vs: List[Variation[A]])(using Mergeable[A]): Node[A] =
|
||||
vs.foldLeft(this)(_.mergeOrAddAsVariation(_))
|
||||
@@ -440,21 +440,21 @@ final case class Variation[A](override val value: A, override val child: Option[
|
||||
f: Node[A] => Option[Node[A]]
|
||||
): HasId[A, Id] ?=> Option[Variation[A]] =
|
||||
path match
|
||||
case Nil => None
|
||||
case head :: Nil if this.hasId(head) => child.map(c => withChild(f(c)))
|
||||
case Nil => None
|
||||
case head :: Nil if this.hasId(head) => child.map(c => withChild(f(c)))
|
||||
case head :: rest if this.hasId(head) =>
|
||||
child.flatMap(_.modifyChildAt(rest, f)) match
|
||||
case None => None
|
||||
case None => None
|
||||
case Some(c) => copy(child = c.some).some
|
||||
case _ => None
|
||||
|
||||
def modifyAt[Id](path: List[Id], f: TreeMapOption[A]): HasId[A, Id] ?=> Option[Variation[A]] =
|
||||
path match
|
||||
case Nil => None
|
||||
case head :: Nil if this.hasId(head) => f(this)
|
||||
case Nil => None
|
||||
case head :: Nil if this.hasId(head) => f(this)
|
||||
case head :: rest if this.hasId(head) =>
|
||||
child.flatMap(_.modifyAt(rest, f)) match
|
||||
case None => None
|
||||
case None => None
|
||||
case Some(c) => copy(child = c.some).some
|
||||
case _ => None
|
||||
|
||||
@@ -463,11 +463,11 @@ final case class Variation[A](override val value: A, override val child: Option[
|
||||
// return Some(None) if the node is deleted
|
||||
def deleteAt[Id](path: List[Id])(using HasId[A, Id]): Option[Option[Variation[A]]] =
|
||||
path match
|
||||
case Nil => None
|
||||
case head :: Nil if this.hasId(head) => Some(None)
|
||||
case Nil => None
|
||||
case head :: Nil if this.hasId(head) => Some(None)
|
||||
case head :: rest if this.hasId(head) =>
|
||||
child.flatMap(_.deleteAt(rest)) match
|
||||
case None => None
|
||||
case None => None
|
||||
case Some(c) => copy(child = c).some.some
|
||||
case _ => None
|
||||
|
||||
@@ -477,7 +477,7 @@ final case class Variation[A](override val value: A, override val child: Option[
|
||||
def mapAccuml[S, B](init: S)(f: (S, A) => (S, B)): (S, Variation[B]) =
|
||||
val (s1, b) = f(init, value)
|
||||
child.map(_.mapAccuml(s1)(f)) match
|
||||
case None => (s1, Variation(b, None))
|
||||
case None => (s1, Variation(b, None))
|
||||
case Some(s) => (s._1, Variation(b, s._2.some))
|
||||
|
||||
// Akin to mapAccuml, return an Option[Variation[B]]
|
||||
@@ -487,16 +487,16 @@ final case class Variation[A](override val value: A, override val child: Option[
|
||||
// should we promote a variation to mainline if the f(value) is None?
|
||||
def mapAccumlOption[S, B](init: S)(f: (S, A) => (S, Option[B])): (S, Option[Variation[B]]) =
|
||||
f(init, value) match
|
||||
case (s1, None) => (s1, None)
|
||||
case (s1, None) => (s1, None)
|
||||
case (s1, Some(b)) =>
|
||||
child.map(_.mapAccumlOption(s1)(f)) match
|
||||
case None => (s1, Variation(b, None).some)
|
||||
case None => (s1, Variation(b, None).some)
|
||||
case Some(s) => (s._1, Variation(b, s._2).some)
|
||||
|
||||
def toNode: Node[A] = Node(value, child)
|
||||
|
||||
object Tree:
|
||||
def lift[A](f: A => A): TreeMapper[A] = tree => tree.withValue(f(tree.value))
|
||||
def lift[A](f: A => A): TreeMapper[A] = tree => tree.withValue(f(tree.value))
|
||||
def liftOption[A](f: A => A): TreeMapOption[A] = tree => tree.withValue(f(tree.value)).some
|
||||
|
||||
extension [A](tm: TreeMapper[A]) def toOption: TreeMapOption[A] = x => tm(x).some
|
||||
@@ -530,7 +530,7 @@ object Tree:
|
||||
@targetName("buildWithNodeReverse")
|
||||
def buildReverse[A, B](s: Seq[A], f: A => Node[B]): Option[Node[B]] =
|
||||
s match
|
||||
case Nil => none
|
||||
case Nil => none
|
||||
case x :: xs => xs.foldLeft(f(x))((acc, a) => f(a).withChild(acc.some)).some
|
||||
|
||||
@targetName("buildWithNode")
|
||||
|
||||
@@ -4,7 +4,7 @@ case class Outcome(winner: Option[Color]):
|
||||
override def toString = winner match
|
||||
case Some(White) => "1-0"
|
||||
case Some(Black) => "0-1"
|
||||
case None => "1/2-1/2"
|
||||
case None => "1/2-1/2"
|
||||
|
||||
object Outcome:
|
||||
|
||||
@@ -16,7 +16,7 @@ object Outcome:
|
||||
|
||||
val white = Outcome(Some(White))
|
||||
val black = Outcome(Some(Black))
|
||||
val draw = Outcome(None)
|
||||
val draw = Outcome(None)
|
||||
|
||||
lazy val knownResultStrings = "*" :: normalizationMap.keys.toList
|
||||
|
||||
@@ -26,21 +26,21 @@ object Outcome:
|
||||
def value: Float = this match
|
||||
case Zero => 0f
|
||||
case Half => 0.5f
|
||||
case One => 1f
|
||||
case One => 1f
|
||||
|
||||
def show: String = this match
|
||||
case Zero => "0"
|
||||
case Half => "1/2"
|
||||
case One => "1"
|
||||
case One => "1"
|
||||
|
||||
import Points.*
|
||||
type GamePoints = ByColor[Points]
|
||||
|
||||
def fromPoints(points: ByColor[Points]): Option[Outcome] = points match
|
||||
case ByColor(One, Zero) => Some(white)
|
||||
case ByColor(Zero, One) => Some(black)
|
||||
case ByColor(One, Zero) => Some(white)
|
||||
case ByColor(Zero, One) => Some(black)
|
||||
case ByColor(Half, Half) => Some(draw)
|
||||
case _ => None
|
||||
case _ => None
|
||||
|
||||
def pointsFromResult(result: String): Option[GamePoints] =
|
||||
normalizationMap.get(result)
|
||||
@@ -48,7 +48,7 @@ object Outcome:
|
||||
def outcomeToPoints(outcome: Outcome): GamePoints = outcome match
|
||||
case Outcome(Some(White)) => ByColor(One, Zero)
|
||||
case Outcome(Some(Black)) => ByColor(Zero, One)
|
||||
case Outcome(None) => ByColor(Half, Half)
|
||||
case Outcome(None) => ByColor(Half, Half)
|
||||
|
||||
def showPoints(points: Option[GamePoints]): String =
|
||||
points.fold("*"):
|
||||
@@ -57,52 +57,52 @@ object Outcome:
|
||||
def guessPointsFromStatusAndPosition(status: Status, positionWinner: Option[Color]): Option[GamePoints] =
|
||||
import Status.*
|
||||
status match
|
||||
case Created | Started => None
|
||||
case Aborted | Cheat | NoStart => Some(ByColor(Zero, Zero))
|
||||
case Stalemate | Draw => Some(ByColor(Half, Half))
|
||||
case Created | Started => None
|
||||
case Aborted | Cheat | NoStart => Some(ByColor(Zero, Zero))
|
||||
case Stalemate | Draw => Some(ByColor(Half, Half))
|
||||
case Mate | Resign | Timeout | Outoftime | UnknownFinish | VariantEnd =>
|
||||
positionWinner match
|
||||
case Some(White) => Some(ByColor(One, Zero))
|
||||
case Some(Black) => Some(ByColor(Zero, One))
|
||||
case None => Some(ByColor(Half, Half))
|
||||
case None => Some(ByColor(Half, Half))
|
||||
|
||||
private val normalizationMap: Map[String, GamePoints] =
|
||||
val hyphen = "-"
|
||||
val enDash = "–"
|
||||
val emDash = "—"
|
||||
val dashes = List(hyphen, enDash, emDash)
|
||||
val hyphen = "-"
|
||||
val enDash = "–"
|
||||
val emDash = "—"
|
||||
val dashes = List(hyphen, enDash, emDash)
|
||||
val separators = dashes ::: List("_", ":")
|
||||
val draws = List("½", "1/2", "0.5")
|
||||
val wins = List("1", "+")
|
||||
val losses = "0" :: dashes
|
||||
val draws = List("½", "1/2", "0.5")
|
||||
val wins = List("1", "+")
|
||||
val losses = "0" :: dashes
|
||||
|
||||
val allDraws = for
|
||||
separator <- separators
|
||||
draw <- draws
|
||||
draw <- draws
|
||||
yield s"$draw$separator$draw"
|
||||
val allWins = for
|
||||
separator <- separators
|
||||
win <- wins
|
||||
loss <- losses
|
||||
win <- wins
|
||||
loss <- losses
|
||||
yield s"$win$separator$loss"
|
||||
val allLosses = for
|
||||
separator <- separators
|
||||
win <- wins
|
||||
loss <- losses
|
||||
win <- wins
|
||||
loss <- losses
|
||||
yield s"$loss$separator$win"
|
||||
val allCancels = for
|
||||
separator <- separators
|
||||
loss <- losses
|
||||
loss <- losses
|
||||
yield s"$loss$separator$loss"
|
||||
val allHalfWins = for
|
||||
separator <- separators
|
||||
loss <- losses
|
||||
draw <- draws
|
||||
loss <- losses
|
||||
draw <- draws
|
||||
yield s"$draw$separator$loss"
|
||||
val allHalfLosses = for
|
||||
separator <- separators
|
||||
loss <- losses
|
||||
draw <- draws
|
||||
loss <- losses
|
||||
draw <- draws
|
||||
yield s"$loss$separator$draw"
|
||||
|
||||
val pairs = allDraws.map(_ -> ByColor[Points](Half, Half)) :::
|
||||
@@ -115,7 +115,7 @@ object Outcome:
|
||||
val lccResults = Map(
|
||||
"WHITEWIN" -> ByColor[Points](One, Zero),
|
||||
"BLACKWIN" -> ByColor[Points](Zero, One),
|
||||
"DRAW" -> ByColor[Points](Half, Half) // ? not sure
|
||||
"DRAW" -> ByColor[Points](Half, Half) // ? not sure
|
||||
)
|
||||
|
||||
pairs.toMap ++ lccResults
|
||||
|
||||
@@ -5,22 +5,22 @@ import cats.derived.*
|
||||
|
||||
case class Piece(color: Color, role: Role) derives Eq:
|
||||
|
||||
def is(c: Color): Boolean = c == color
|
||||
def is(r: Role): Boolean = r == role
|
||||
def is(c: Color): Boolean = c == color
|
||||
def is(r: Role): Boolean = r == role
|
||||
def isNot(r: Role): Boolean = r != role
|
||||
def unary_! : Piece = Piece(!color, role)
|
||||
def unary_! : Piece = Piece(!color, role)
|
||||
|
||||
def forsyth: Char = if color.white then role.forsythUpper else role.forsyth
|
||||
|
||||
// the piece at from can attack the target to when mask are all the occupied squares
|
||||
def eyes(from: Square, to: Square, mask: Bitboard): Boolean =
|
||||
role match
|
||||
case King => from.kingAttacks.contains(to)
|
||||
case Queen => from.queenAttacks(mask).contains(to)
|
||||
case Rook => from.rookAttacks(mask).contains(to)
|
||||
case King => from.kingAttacks.contains(to)
|
||||
case Queen => from.queenAttacks(mask).contains(to)
|
||||
case Rook => from.rookAttacks(mask).contains(to)
|
||||
case Bishop => from.bishopAttacks(mask).contains(to)
|
||||
case Knight => from.knightAttacks.contains(to)
|
||||
case Pawn => from.pawnAttacks(color).contains(to)
|
||||
case Pawn => from.pawnAttacks(color).contains(to)
|
||||
|
||||
override def toString = s"$color-$role".toLowerCase
|
||||
|
||||
|
||||
@@ -11,49 +11,49 @@ object PlayerTitle:
|
||||
|
||||
private inline def apply(inline s: String): PlayerTitle = s.asInstanceOf[PlayerTitle]
|
||||
extension (t: PlayerTitle)
|
||||
inline def value: String = t
|
||||
def isLichess: Boolean = t == "LM" || t == "BOT"
|
||||
inline def value: String = t
|
||||
def isLichess: Boolean = t == "LM" || t == "BOT"
|
||||
def isFederation: Boolean = !isLichess
|
||||
|
||||
given Render[PlayerTitle] = _.value
|
||||
|
||||
val GM: PlayerTitle = "GM"
|
||||
val GM: PlayerTitle = "GM"
|
||||
val WGM: PlayerTitle = "WGM"
|
||||
val IM: PlayerTitle = "IM"
|
||||
val IM: PlayerTitle = "IM"
|
||||
val WIM: PlayerTitle = "WIM"
|
||||
val FM: PlayerTitle = "FM"
|
||||
val FM: PlayerTitle = "FM"
|
||||
val WFM: PlayerTitle = "WFM"
|
||||
val NM: PlayerTitle = "NM"
|
||||
val CM: PlayerTitle = "CM"
|
||||
val NM: PlayerTitle = "NM"
|
||||
val CM: PlayerTitle = "CM"
|
||||
val WCM: PlayerTitle = "WCM"
|
||||
val WNM: PlayerTitle = "WNM"
|
||||
val LM: PlayerTitle = "LM"
|
||||
val LM: PlayerTitle = "LM"
|
||||
val BOT: PlayerTitle = "BOT"
|
||||
|
||||
// names are as stated on FIDE profile pages
|
||||
val all = List[(PlayerTitle, String)](
|
||||
GM -> "Grandmaster",
|
||||
GM -> "Grandmaster",
|
||||
WGM -> "Woman Grandmaster",
|
||||
IM -> "International Master",
|
||||
IM -> "International Master",
|
||||
WIM -> "Woman Intl. Master",
|
||||
FM -> "FIDE Master",
|
||||
FM -> "FIDE Master",
|
||||
WFM -> "Woman FIDE Master",
|
||||
NM -> "National Master",
|
||||
CM -> "Candidate Master",
|
||||
NM -> "National Master",
|
||||
CM -> "Candidate Master",
|
||||
WCM -> "Woman Candidate Master",
|
||||
WNM -> "Woman National Master",
|
||||
LM -> "Lichess Master",
|
||||
LM -> "Lichess Master",
|
||||
BOT -> "Chess Robot"
|
||||
)
|
||||
|
||||
val names: Map[PlayerTitle, String] = all.toMap
|
||||
val names: Map[PlayerTitle, String] = all.toMap
|
||||
lazy val fromNames: Map[String, PlayerTitle] = all.map(_.swap).toMap
|
||||
|
||||
val acronyms: List[PlayerTitle] = all.map(_._1)
|
||||
|
||||
def titleName(title: PlayerTitle): String = names.getOrElse(title, title.value)
|
||||
|
||||
def get(str: String): Option[PlayerTitle] = Option(PlayerTitle(str.toUpperCase)).filter(names.contains)
|
||||
def get(str: String): Option[PlayerTitle] = Option(PlayerTitle(str.toUpperCase)).filter(names.contains)
|
||||
def get(strs: List[String]): List[PlayerTitle] = strs.flatMap { get(_) }
|
||||
|
||||
// ordered by difficulty to achieve
|
||||
|
||||
@@ -33,7 +33,7 @@ case class Position(board: Board, history: History, variant: Variant, color: Col
|
||||
if v == Crazyhouse then copy(variant = v).ensureCrazyData
|
||||
else copy(variant = v)
|
||||
|
||||
def withCrazyData(data: Crazyhouse.Data): Position = updateHistory(_.copy(crazyData = data.some))
|
||||
def withCrazyData(data: Crazyhouse.Data): Position = updateHistory(_.copy(crazyData = data.some))
|
||||
def withCrazyData(data: Option[Crazyhouse.Data]): Position = updateHistory(_.copy(crazyData = data))
|
||||
def withCrazyData(f: Crazyhouse.Data => Crazyhouse.Data): Position =
|
||||
withCrazyData(f(crazyData.getOrElse(Crazyhouse.Data.init)))
|
||||
@@ -60,7 +60,7 @@ case class Position(board: Board, history: History, variant: Variant, color: Col
|
||||
def drops: Option[List[Square]] =
|
||||
variant match
|
||||
case v: Crazyhouse.type => v.possibleDrops(this)
|
||||
case _ => None
|
||||
case _ => None
|
||||
|
||||
def checkSquare: Option[Square] = if check.yes then ourKing else None
|
||||
|
||||
@@ -94,7 +94,7 @@ case class Position(board: Board, history: History, variant: Variant, color: Col
|
||||
inline def variantEnd: Boolean = variant.specialEnd(this)
|
||||
|
||||
@threadUnsafe
|
||||
lazy val check: Check = checkOf(color)
|
||||
lazy val check: Check = checkOf(color)
|
||||
inline def checkOf(c: Color): Check = variant.kingThreatened(board, c)
|
||||
|
||||
@threadUnsafe
|
||||
@@ -187,7 +187,7 @@ case class Position(board: Board, history: History, variant: Variant, color: Col
|
||||
|
||||
val s1: List[Move] = for
|
||||
from <- capturers
|
||||
to <- from.pawnAttacks(color) & them & mask
|
||||
to <- from.pawnAttacks(color) & them & mask
|
||||
move <- genPawnMoves(from, to, true)
|
||||
yield move
|
||||
|
||||
@@ -204,13 +204,13 @@ case class Position(board: Board, history: History, variant: Variant, color: Col
|
||||
else Bitboard.rank(color.fourthRank))
|
||||
|
||||
val s2: List[Move] = for
|
||||
to <- singleMoves & mask
|
||||
to <- singleMoves & mask
|
||||
from <- Square(to.value + (if isWhiteTurn then -8 else 8)).toList
|
||||
move <- genPawnMoves(from, to, false)
|
||||
yield move
|
||||
|
||||
val s3: List[Move] = for
|
||||
to <- doubleMoves & mask
|
||||
to <- doubleMoves & mask
|
||||
from <- Square(to.value + (if isWhiteTurn then -16 else 16))
|
||||
move <- normalMove(from, to, Pawn, false)
|
||||
yield move
|
||||
@@ -220,28 +220,28 @@ case class Position(board: Board, history: History, variant: Variant, color: Col
|
||||
def genKnight(knights: Bitboard, mask: Bitboard): List[Move] =
|
||||
for
|
||||
from <- knights
|
||||
to <- from.knightAttacks & mask
|
||||
to <- from.knightAttacks & mask
|
||||
move <- normalMove(from, to, Knight, isOccupied(to))
|
||||
yield move
|
||||
|
||||
def genBishop(bishops: Bitboard, mask: Bitboard): List[Move] =
|
||||
for
|
||||
from <- bishops
|
||||
to <- from.bishopAttacks(board.occupied) & mask
|
||||
to <- from.bishopAttacks(board.occupied) & mask
|
||||
move <- normalMove(from, to, Bishop, isOccupied(to))
|
||||
yield move
|
||||
|
||||
def genRook(rooks: Bitboard, mask: Bitboard): List[Move] =
|
||||
for
|
||||
from <- rooks
|
||||
to <- from.rookAttacks(board.occupied) & mask
|
||||
to <- from.rookAttacks(board.occupied) & mask
|
||||
move <- normalMove(from, to, Rook, isOccupied(to))
|
||||
yield move
|
||||
|
||||
def genQueen(queens: Bitboard, mask: Bitboard): List[Move] =
|
||||
for
|
||||
from <- queens
|
||||
to <- from.queenAttacks(board.occupied) & mask
|
||||
to <- from.queenAttacks(board.occupied) & mask
|
||||
move <- normalMove(from, to, Queen, isOccupied(to))
|
||||
yield move
|
||||
|
||||
@@ -268,8 +268,8 @@ case class Position(board: Board, history: History, variant: Variant, color: Col
|
||||
|| (rook.value > king.value && history.castles.can(color, KingSide))
|
||||
toKingFile = if rook.value < king.value then File.C else File.G
|
||||
toRookFile = if rook.value < king.value then File.D else File.F
|
||||
kingTo = Square(toKingFile, king.rank)
|
||||
rookTo = Square(toRookFile, rook.rank)
|
||||
kingTo = Square(toKingFile, king.rank)
|
||||
rookTo = Square(toRookFile, rook.rank)
|
||||
// calulate different path for standard vs chess960
|
||||
path =
|
||||
if variant.chess960 || variant.fromPosition
|
||||
@@ -364,9 +364,9 @@ case class Position(board: Board, history: History, variant: Variant, color: Col
|
||||
private def castle(king: Square, kingTo: Square, rook: Square, rookTo: Square): List[Move] =
|
||||
|
||||
val boardAfter = for
|
||||
b1 <- board.take(king)
|
||||
b2 <- b1.take(rook)
|
||||
b3 <- b2.put(color.king, kingTo)
|
||||
b1 <- board.take(king)
|
||||
b2 <- b1.take(rook)
|
||||
b3 <- b2.put(color.king, kingTo)
|
||||
after <- b3.put(color.rook, rookTo)
|
||||
yield after
|
||||
|
||||
@@ -378,7 +378,7 @@ case class Position(board: Board, history: History, variant: Variant, color: Col
|
||||
val destInput = if !isChess960 then List(rook, kingTo) else List(rook)
|
||||
|
||||
for
|
||||
after <- boardAfter.map(withBoard).toList
|
||||
after <- boardAfter.map(withBoard).toList
|
||||
inputKingSquare <- destInput
|
||||
yield Move(
|
||||
piece = color.king,
|
||||
@@ -395,7 +395,7 @@ case class Position(board: Board, history: History, variant: Variant, color: Col
|
||||
object Position:
|
||||
|
||||
case class AndFullMoveNumber(position: Position, fullMoveNumber: FullMoveNumber):
|
||||
def ply: Ply = fullMoveNumber.ply(position.color)
|
||||
def ply: Ply = fullMoveNumber.ply(position.color)
|
||||
def toGame: Game = Game(position = position, ply = ply, startedAtPly = ply)
|
||||
|
||||
object AndFullMoveNumber:
|
||||
|
||||
@@ -5,8 +5,8 @@ object Rank:
|
||||
extension (a: Rank)
|
||||
inline def value: Int = a
|
||||
|
||||
inline infix def >(inline o: Rank): Boolean = value > o.value
|
||||
inline infix def <(inline o: Rank): Boolean = value < o.value
|
||||
inline infix def >(inline o: Rank): Boolean = value > o.value
|
||||
inline infix def <(inline o: Rank): Boolean = value < o.value
|
||||
inline infix def >=(inline o: Rank): Boolean = value >= o.value
|
||||
inline infix def <=(inline o: Rank): Boolean = value <= o.value
|
||||
|
||||
@@ -22,14 +22,14 @@ object Rank:
|
||||
|
||||
inline def fromChar(inline ch: Char): Option[Rank] = Rank(ch.toInt - 49)
|
||||
|
||||
val First: Rank = 0
|
||||
val Second: Rank = 1
|
||||
val Third: Rank = 2
|
||||
val Fourth: Rank = 3
|
||||
val Fifth: Rank = 4
|
||||
val Sixth: Rank = 5
|
||||
val First: Rank = 0
|
||||
val Second: Rank = 1
|
||||
val Third: Rank = 2
|
||||
val Fourth: Rank = 3
|
||||
val Fifth: Rank = 4
|
||||
val Sixth: Rank = 5
|
||||
val Seventh: Rank = 6
|
||||
val Eighth: Rank = 7
|
||||
val Eighth: Rank = 7
|
||||
|
||||
val all = List(First, Second, Third, Fourth, Fifth, Sixth, Seventh, Eighth)
|
||||
val all = List(First, Second, Third, Fourth, Fifth, Sixth, Seventh, Eighth)
|
||||
val allReversed = all.reverse
|
||||
|
||||
@@ -6,7 +6,7 @@ object Rated extends YesNo[Rated]:
|
||||
|
||||
extension (r: Rated)
|
||||
def name: String = if r then "rated" else "casual"
|
||||
def id: Int = if r then 1 else 0
|
||||
def id: Int = if r then 1 else 0
|
||||
|
||||
val all: List[Rated] = List(No, Yes)
|
||||
|
||||
|
||||
@@ -34,19 +34,19 @@ object Replay:
|
||||
else
|
||||
// we don't want to compare the full move number, to match transpositions
|
||||
def truncateFen(fen: Fen.Full) = fen.value.split(' ').take(4).mkString(" ")
|
||||
val atFenTruncated = truncateFen(atFen)
|
||||
def compareFen(fen: Fen.Full) = truncateFen(fen) == atFenTruncated
|
||||
val atFenTruncated = truncateFen(atFen)
|
||||
def compareFen(fen: Fen.Full) = truncateFen(fen) == atFenTruncated
|
||||
|
||||
@scala.annotation.tailrec
|
||||
def recursivePlyAtFen(position: Position, sans: List[San], ply: Ply): Either[ErrorStr, Ply] =
|
||||
sans match
|
||||
case Nil => ErrorStr(s"Can't find $atFenTruncated, reached ply $ply").asLeft
|
||||
case Nil => ErrorStr(s"Can't find $atFenTruncated, reached ply $ply").asLeft
|
||||
case san :: rest =>
|
||||
san(position) match
|
||||
case Left(err) => err.asLeft
|
||||
case Left(err) => err.asLeft
|
||||
case Right(moveOrDrop) =>
|
||||
val after = moveOrDrop.after
|
||||
val fen = Fen.write(after.withColor(ply.turn), ply.fullMoveNumber)
|
||||
val fen = Fen.write(after.withColor(ply.turn), ply.fullMoveNumber)
|
||||
if compareFen(fen) then ply.asRight
|
||||
else recursivePlyAtFen(after.withColor(!position.color), rest, ply.next)
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ package chess
|
||||
|
||||
sealed trait Role:
|
||||
val forsyth: Char
|
||||
lazy val forsythUpper: Char = forsyth.toUpper
|
||||
lazy val pgn: Char = forsythUpper
|
||||
lazy val name = toString.toLowerCase
|
||||
lazy val forsythUpper: Char = forsyth.toUpper
|
||||
lazy val pgn: Char = forsythUpper
|
||||
lazy val name = toString.toLowerCase
|
||||
inline def forsythBy(color: Color): Char =
|
||||
if color.white then forsythUpper else forsyth
|
||||
|
||||
@@ -31,14 +31,14 @@ case object Pawn extends Role:
|
||||
|
||||
object Role:
|
||||
|
||||
val all: List[Role] = List(King, Queen, Rook, Bishop, Knight, Pawn)
|
||||
val allPromotable: List[PromotableRole] = List(Queen, Rook, Bishop, Knight, King)
|
||||
val allByForsyth: Map[Char, Role] = all.mapBy(_.forsyth)
|
||||
val allByPgn: Map[Char, Role] = all.mapBy(_.pgn)
|
||||
val allByName: Map[String, Role] = all.mapBy(_.name)
|
||||
val allPromotableByName: Map[String, PromotableRole] = allPromotable.mapBy(_.toString)
|
||||
val all: List[Role] = List(King, Queen, Rook, Bishop, Knight, Pawn)
|
||||
val allPromotable: List[PromotableRole] = List(Queen, Rook, Bishop, Knight, King)
|
||||
val allByForsyth: Map[Char, Role] = all.mapBy(_.forsyth)
|
||||
val allByPgn: Map[Char, Role] = all.mapBy(_.pgn)
|
||||
val allByName: Map[String, Role] = all.mapBy(_.name)
|
||||
val allPromotableByName: Map[String, PromotableRole] = allPromotable.mapBy(_.toString)
|
||||
val allPromotableByForsyth: Map[Char, PromotableRole] = allPromotable.mapBy(_.forsyth)
|
||||
val allPromotableByPgn: Map[Char, PromotableRole] = allPromotable.mapBy(_.pgn)
|
||||
val allPromotableByPgn: Map[Char, PromotableRole] = allPromotable.mapBy(_.pgn)
|
||||
|
||||
def forsyth(c: Char): Option[Role] = allByForsyth.get(c)
|
||||
|
||||
@@ -53,9 +53,9 @@ object Role:
|
||||
|
||||
def valueOf(r: Role): Option[Int] =
|
||||
r match
|
||||
case Pawn => Option(1)
|
||||
case Pawn => Option(1)
|
||||
case Knight => Option(3)
|
||||
case Bishop => Option(3)
|
||||
case Rook => Option(5)
|
||||
case Queen => Option(9)
|
||||
case King => None
|
||||
case Rook => Option(5)
|
||||
case Queen => Option(9)
|
||||
case King => None
|
||||
|
||||
@@ -23,9 +23,9 @@ object Square:
|
||||
@targetName("aboveOf")
|
||||
inline def ?^(inline other: Square): Boolean = rank > other.rank
|
||||
|
||||
inline def onSameFile(inline other: Square): Boolean = file == other.file
|
||||
inline def onSameRank(inline other: Square): Boolean = rank == other.rank
|
||||
inline def onSameLine(inline other: Square): Boolean = onSameFile(other) || onSameRank(other)
|
||||
inline def onSameFile(inline other: Square): Boolean = file == other.file
|
||||
inline def onSameRank(inline other: Square): Boolean = rank == other.rank
|
||||
inline def onSameLine(inline other: Square): Boolean = onSameFile(other) || onSameRank(other)
|
||||
inline def onSameDiagonal(inline other: Square): Boolean =
|
||||
file.value - rank.value == other.file.value - other.rank.value || file.value + rank.value == other.file.value + other.rank.value
|
||||
|
||||
@@ -38,9 +38,9 @@ object Square:
|
||||
inline def rank: Rank = Rank.of(s)
|
||||
|
||||
def asChar: Char =
|
||||
if s <= 25 then (97 + s).toChar // a ...
|
||||
if s <= 25 then (97 + s).toChar // a ...
|
||||
else if s <= 51 then (39 + s).toChar // A ...
|
||||
else if s <= 61 then (s - 4).toChar // 0 ...
|
||||
else if s <= 61 then (s - 4).toChar // 0 ...
|
||||
else if s == 62 then '!'
|
||||
else '?'
|
||||
|
||||
@@ -53,7 +53,7 @@ object Square:
|
||||
inline def withFileOf(inline o: Square): Square = withFile(o.file)
|
||||
|
||||
inline def bb: Bitboard = Bitboard(1L << s.value)
|
||||
inline def bl: Long = 1L << s.value
|
||||
inline def bl: Long = 1L << s.value
|
||||
|
||||
def bishopAttacks(occupied: Bitboard): Bitboard =
|
||||
Bitboard(ATTACKS(Magic.BISHOP(s.value).bishopIndex(occupied.value)))
|
||||
@@ -75,7 +75,7 @@ object Square:
|
||||
|
||||
inline def apply(inline file: File, inline rank: Rank): Square = file.value + 8 * rank.value
|
||||
|
||||
inline def apply(index: Int): Option[Square] = Option.when(0 <= index && index < 64)(index)
|
||||
inline def apply(index: Int): Option[Square] = Option.when(0 <= index && index < 64)(index)
|
||||
private[chess] def unsafe(index: Int): Square = index
|
||||
|
||||
inline def at(x: Int, y: Int): Option[Square] = Option.when(0 <= x && x < 8 && 0 <= y && y < 8)(x + 8 * y)
|
||||
@@ -154,4 +154,4 @@ object Square:
|
||||
val all: List[Square] = (0 to 63).toList
|
||||
|
||||
val allKeys: Map[String, Square] = all.mapBy(_.key)
|
||||
val charMap: Map[Char, Square] = all.mapBy(_.asChar)
|
||||
val charMap: Map[Char, Square] = all.mapBy(_.asChar)
|
||||
|
||||
@@ -209,5 +209,5 @@ object StartingPosition:
|
||||
OpeningDb.findByStandardFen(fen).map(StartingPosition(_, featurable))
|
||||
|
||||
object presets:
|
||||
val halloween = of(StandardFen("r1bqkb1r/pppp1ppp/2n2n2/4N3/4P3/2N5/PPPP1PPP/R1BQKB1R b KQkq -"))
|
||||
val halloween = of(StandardFen("r1bqkb1r/pppp1ppp/2n2n2/4N3/4P3/2N5/PPPP1PPP/R1BQKB1R b KQkq -"))
|
||||
val frankenstein = of(StandardFen("rnbqkb1r/pppp1ppp/8/4p3/2B1n3/2N5/PPPP1PPP/R1BQK1NR w KQkq -"))
|
||||
|
||||
@@ -5,9 +5,9 @@ final case class Stats(samples: Int, mean: Float, sn: Float):
|
||||
|
||||
def record(value: Float) =
|
||||
val newSamples = samples + 1
|
||||
val delta = value - mean
|
||||
val newMean = mean + delta / newSamples
|
||||
val newSN = sn + delta * (value - newMean)
|
||||
val delta = value - mean
|
||||
val newMean = mean + delta / newSamples
|
||||
val newSN = sn + delta * (value - newMean)
|
||||
|
||||
Stats(
|
||||
samples = newSamples,
|
||||
@@ -27,6 +27,6 @@ final case class Stats(samples: Int, mean: Float, sn: Float):
|
||||
def total = samples * mean
|
||||
|
||||
object Stats:
|
||||
val empty: Stats = Stats(0, 0, 0)
|
||||
def apply(value: Float): Stats = empty.record(value)
|
||||
val empty: Stats = Stats(0, 0, 0)
|
||||
def apply(value: Float): Stats = empty.record(value)
|
||||
def apply[T: Numeric](values: Iterable[T]): Stats = empty.record(values)
|
||||
|
||||
@@ -4,27 +4,27 @@ enum Status(val id: Int):
|
||||
|
||||
val name = s"${toString.head.toLower}${toString.tail}"
|
||||
|
||||
inline def is(inline s: Status): Boolean = this == s
|
||||
inline def is(inline s: Status): Boolean = this == s
|
||||
inline def is(inline f: Status.type => Status): Boolean = is(f(Status))
|
||||
|
||||
inline infix def >=(inline s: Status): Boolean = id >= s.id
|
||||
inline infix def >(inline s: Status): Boolean = id > s.id
|
||||
inline infix def >(inline s: Status): Boolean = id > s.id
|
||||
inline infix def <=(inline s: Status): Boolean = id <= s.id
|
||||
inline infix def <(inline s: Status): Boolean = id < s.id
|
||||
inline infix def <(inline s: Status): Boolean = id < s.id
|
||||
|
||||
case Created extends Status(10)
|
||||
case Started extends Status(20)
|
||||
case Aborted extends Status(25) // from this point the game is finished
|
||||
case Mate extends Status(30)
|
||||
case Resign extends Status(31)
|
||||
case Stalemate extends Status(32)
|
||||
case Timeout extends Status(33) // when player leaves the game
|
||||
case Draw extends Status(34)
|
||||
case Outoftime extends Status(35) // clock flag
|
||||
case Cheat extends Status(36)
|
||||
case NoStart extends Status(37) // the player did not make the first move in time
|
||||
case Created extends Status(10)
|
||||
case Started extends Status(20)
|
||||
case Aborted extends Status(25) // from this point the game is finished
|
||||
case Mate extends Status(30)
|
||||
case Resign extends Status(31)
|
||||
case Stalemate extends Status(32)
|
||||
case Timeout extends Status(33) // when player leaves the game
|
||||
case Draw extends Status(34)
|
||||
case Outoftime extends Status(35) // clock flag
|
||||
case Cheat extends Status(36)
|
||||
case NoStart extends Status(37) // the player did not make the first move in time
|
||||
case UnknownFinish extends Status(38) // we don't know why the game ended
|
||||
case VariantEnd extends Status(60) // the variant has a special ending
|
||||
case VariantEnd extends Status(60) // the variant has a special ending
|
||||
|
||||
object Status:
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import Clock.{ LimitSeconds, LimitMinutes, IncrementSeconds }
|
||||
|
||||
case class TournamentClock(limitSeconds: LimitSeconds, incrementSeconds: IncrementSeconds):
|
||||
|
||||
def limit: Centis = Centis.ofSeconds(limitSeconds.value)
|
||||
def limit: Centis = Centis.ofSeconds(limitSeconds.value)
|
||||
def increment: Centis = Centis.ofSeconds(incrementSeconds.value)
|
||||
|
||||
def limitMinutes = LimitMinutes(limitSeconds.value / 60)
|
||||
@@ -32,6 +32,6 @@ object TournamentClock:
|
||||
.replaceAllIn(str.toLowerCase.replace(" ", ""), "")
|
||||
.split('+')
|
||||
.match
|
||||
case Array(a) => a.toIntOption.map(make(strict)(_, 0))
|
||||
case Array(a) => a.toIntOption.map(make(strict)(_, 0))
|
||||
case Array(a, b) => (a.toIntOption, b.toIntOption).mapN(make(strict))
|
||||
case _ => none
|
||||
case _ => none
|
||||
|
||||
@@ -7,11 +7,11 @@ object UnmovedRooks:
|
||||
// for lila testing only
|
||||
val default: UnmovedRooks = UnmovedRooks(Bitboard.rank(Rank.First) | Bitboard.rank(Rank.Eighth))
|
||||
val corners: UnmovedRooks = 0x8100000000000081L
|
||||
val none: UnmovedRooks = 0L
|
||||
val none: UnmovedRooks = 0L
|
||||
|
||||
@targetName("applyUnmovedRooks")
|
||||
def apply(b: Bitboard): UnmovedRooks = b.value
|
||||
def apply(l: Long): UnmovedRooks = l
|
||||
def apply(b: Bitboard): UnmovedRooks = b.value
|
||||
def apply(l: Long): UnmovedRooks = l
|
||||
inline def apply(inline xs: Iterable[Square]): UnmovedRooks = xs.foldLeft(none)((b, s) => b | s.bl)
|
||||
|
||||
// guess unmovedRooks from board
|
||||
@@ -22,9 +22,9 @@ object UnmovedRooks:
|
||||
UnmovedRooks(wr | br)
|
||||
|
||||
extension (ur: UnmovedRooks)
|
||||
inline def bb: Bitboard = Bitboard(ur)
|
||||
def isEmpty = ur == 0L
|
||||
def value: Long = ur
|
||||
inline def bb: Bitboard = Bitboard(ur)
|
||||
def isEmpty = ur == 0L
|
||||
def value: Long = ur
|
||||
def toList: List[Square] = ur.bb.squares
|
||||
|
||||
def without(color: Color): UnmovedRooks =
|
||||
@@ -51,7 +51,7 @@ object UnmovedRooks:
|
||||
def contains(square: Square): Boolean =
|
||||
(ur & (1L << square.value)) != 0L
|
||||
|
||||
inline def unary_~ : UnmovedRooks = ~ur
|
||||
inline def unary_~ : UnmovedRooks = ~ur
|
||||
inline infix def &(inline o: Long): UnmovedRooks = ur & o
|
||||
inline infix def ^(inline o: Long): UnmovedRooks = ur ^ o
|
||||
inline infix def |(inline o: Long): UnmovedRooks = ur | o
|
||||
|
||||
@@ -61,7 +61,7 @@ object Attacks:
|
||||
var subset = 0L
|
||||
while
|
||||
val attack = slidingAttacks(square, subset, deltas)
|
||||
val idx = ((magic.factor * subset) >>> (64 - shift)).toInt + magic.offset
|
||||
val idx = ((magic.factor * subset) >>> (64 - shift)).toInt + magic.offset
|
||||
ATTACKS(idx) = attack
|
||||
|
||||
// Carry-rippler trick for enumerating subsets.
|
||||
|
||||
@@ -5,7 +5,7 @@ import scala.annotation.static
|
||||
|
||||
class Magic(val mask: Long, val factor: Long, val offset: Int):
|
||||
def bishopIndex(occupied: Long): Int = (factor * (occupied & mask) >>> (64 - 9)).toInt + offset
|
||||
def rookIndex(occupied: Long): Int = (factor * (occupied & mask) >>> (64 - 12)).toInt + offset
|
||||
def rookIndex(occupied: Long): Int = (factor * (occupied & mask) >>> (64 - 12)).toInt + offset
|
||||
|
||||
object Magic:
|
||||
@static
|
||||
|
||||
@@ -8,34 +8,34 @@ enum Score:
|
||||
case Mate(m: Eval.Mate)
|
||||
|
||||
inline def fold[A](w: Eval.Cp => A, b: Eval.Mate => A): A = this match
|
||||
case Cp(cp) => w(cp)
|
||||
case Cp(cp) => w(cp)
|
||||
case Mate(mate) => b(mate)
|
||||
|
||||
inline def cp: Option[Eval.Cp] = fold(Some(_), _ => None)
|
||||
inline def cp: Option[Eval.Cp] = fold(Some(_), _ => None)
|
||||
inline def mate: Option[Eval.Mate] = fold(_ => None, Some(_))
|
||||
|
||||
inline def isCheckmate = mate.exists(_.value == 0)
|
||||
inline def mateFound = mate.isDefined
|
||||
inline def mateFound = mate.isDefined
|
||||
|
||||
inline def invert: Score = fold(c => Cp(c.invert), m => Mate(m.invert))
|
||||
inline def invert: Score = fold(c => Cp(c.invert), m => Mate(m.invert))
|
||||
inline def invertIf(cond: Boolean): Score = if cond then invert else this
|
||||
|
||||
object Score:
|
||||
val initial = Cp(Eval.Cp.initial)
|
||||
def cp(cp: Int): Score = Cp(Eval.Cp(cp))
|
||||
val initial = Cp(Eval.Cp.initial)
|
||||
def cp(cp: Int): Score = Cp(Eval.Cp(cp))
|
||||
def mate(mate: Int): Score = Mate(Eval.Mate(mate))
|
||||
|
||||
object Eval:
|
||||
opaque type Cp = Int
|
||||
object Cp extends OpaqueInt[Cp]:
|
||||
val CEILING = Cp(1000)
|
||||
val initial = Cp(15)
|
||||
val CEILING = Cp(1000)
|
||||
val initial = Cp(15)
|
||||
inline def ceilingWithSignum(signum: Int) = CEILING.invertIf(signum < 0)
|
||||
|
||||
extension (cp: Cp)
|
||||
inline def centipawns = cp.value
|
||||
|
||||
inline def pawns: Float = cp.value / 100f
|
||||
inline def pawns: Float = cp.value / 100f
|
||||
inline def showPawns: String = "%.2f".format(pawns)
|
||||
|
||||
inline def ceiled: Cp =
|
||||
@@ -43,7 +43,7 @@ object Eval:
|
||||
else if cp.value < -Cp.CEILING then -Cp.CEILING
|
||||
else cp
|
||||
|
||||
inline def invert: Cp = Cp(-cp.value)
|
||||
inline def invert: Cp = Cp(-cp.value)
|
||||
inline def invertIf(cond: Boolean): Cp = if cond then invert else cp
|
||||
|
||||
def signum: Int = Math.signum(cp.value.toFloat).toInt
|
||||
@@ -55,7 +55,7 @@ object Eval:
|
||||
extension (mate: Mate)
|
||||
inline def moves: Int = mate.value
|
||||
|
||||
inline def invert: Mate = Mate(-moves)
|
||||
inline def invert: Mate = Mate(-moves)
|
||||
inline def invertIf(cond: Boolean): Mate = if cond then invert else mate
|
||||
|
||||
inline def signum: Int = if positive then 1 else -1
|
||||
|
||||
@@ -11,23 +11,23 @@ case class BinaryFen(value: Array[Byte]) extends AnyVal:
|
||||
|
||||
def read: Position.AndFullMoveNumber =
|
||||
val reader = new Iterator[Byte]:
|
||||
val inner = value.iterator
|
||||
val inner = value.iterator
|
||||
override inline def hasNext: Boolean = inner.hasNext
|
||||
override inline def next: Byte = if hasNext then inner.next else 0.toByte
|
||||
override inline def next: Byte = if hasNext then inner.next else 0.toByte
|
||||
|
||||
val occupied = Bitboard(readLong(reader))
|
||||
|
||||
var pawns = Bitboard.empty
|
||||
var pawns = Bitboard.empty
|
||||
var knights = Bitboard.empty
|
||||
var bishops = Bitboard.empty
|
||||
var rooks = Bitboard.empty
|
||||
var queens = Bitboard.empty
|
||||
var kings = Bitboard.empty
|
||||
var white = Bitboard.empty
|
||||
var black = Bitboard.empty
|
||||
var rooks = Bitboard.empty
|
||||
var queens = Bitboard.empty
|
||||
var kings = Bitboard.empty
|
||||
var white = Bitboard.empty
|
||||
var black = Bitboard.empty
|
||||
|
||||
var turn = White
|
||||
var unmovedRooks = UnmovedRooks(Bitboard.empty)
|
||||
var turn = White
|
||||
var unmovedRooks = UnmovedRooks(Bitboard.empty)
|
||||
var epMove: Option[Uci.Move] = None
|
||||
|
||||
def unpackPiece(sq: Square, nibble: Int) =
|
||||
@@ -96,8 +96,8 @@ case class BinaryFen(value: Array[Byte]) extends AnyVal:
|
||||
if it.hasNext then unpackPiece(it.next, hi)
|
||||
|
||||
val halfMoveClock = HalfMoveClock(readLeb128(reader))
|
||||
val ply = Ply(readLeb128(reader))
|
||||
val variant = reader.next match
|
||||
val ply = Ply(readLeb128(reader))
|
||||
val variant = reader.next match
|
||||
case 0 => Standard
|
||||
case 1 => Crazyhouse
|
||||
case 2 => Chess960
|
||||
@@ -165,10 +165,10 @@ case class BinaryFen(value: Array[Byte]) extends AnyVal:
|
||||
ply.fullMoveNumber
|
||||
)
|
||||
|
||||
override def hashCode: Int = value.toSeq.hashCode
|
||||
override def hashCode: Int = value.toSeq.hashCode
|
||||
override def equals(that: Any): Boolean = that match
|
||||
case thatFen: BinaryFen => value.toSeq.equals(thatFen.value.toSeq)
|
||||
case _ => false
|
||||
case _ => false
|
||||
|
||||
object BinaryFen:
|
||||
|
||||
@@ -181,7 +181,7 @@ object BinaryFen:
|
||||
.updateHistory(_.setHalfMoveClock(HalfMoveClock.initial))
|
||||
.withVariant(position.variant match
|
||||
case Standard | Chess960 | FromPosition => Standard
|
||||
case other => other),
|
||||
case other => other),
|
||||
FullMoveNumber.initial
|
||||
)
|
||||
)
|
||||
@@ -202,38 +202,38 @@ object BinaryFen:
|
||||
// Encoding from
|
||||
// https://github.com/official-stockfish/nnue-pytorch/blob/2db3787d2e36f7142ea4d0e307b502dda4095cd9/lib/nnue_training_data_formats.h#L4607
|
||||
case Some(Piece(_, Pawn)) if pawnPushedTo.contains(sq) => 12
|
||||
case Some(Piece(White, Pawn)) => 0
|
||||
case Some(Piece(Black, Pawn)) => 1
|
||||
case Some(Piece(White, Knight)) => 2
|
||||
case Some(Piece(Black, Knight)) => 3
|
||||
case Some(Piece(White, Bishop)) => 4
|
||||
case Some(Piece(Black, Bishop)) => 5
|
||||
case Some(Piece(White, Rook)) => if unmovedRooks.contains(sq) then 13 else 6
|
||||
case Some(Piece(Black, Rook)) => if unmovedRooks.contains(sq) then 14 else 7
|
||||
case Some(Piece(White, Queen)) => 8
|
||||
case Some(Piece(Black, Queen)) => 9
|
||||
case Some(Piece(White, King)) => 10
|
||||
case Some(Piece(Black, King)) => if position.color.white then 11 else 15
|
||||
case None => 0 // unreachable
|
||||
case Some(Piece(White, Pawn)) => 0
|
||||
case Some(Piece(Black, Pawn)) => 1
|
||||
case Some(Piece(White, Knight)) => 2
|
||||
case Some(Piece(Black, Knight)) => 3
|
||||
case Some(Piece(White, Bishop)) => 4
|
||||
case Some(Piece(Black, Bishop)) => 5
|
||||
case Some(Piece(White, Rook)) => if unmovedRooks.contains(sq) then 13 else 6
|
||||
case Some(Piece(Black, Rook)) => if unmovedRooks.contains(sq) then 14 else 7
|
||||
case Some(Piece(White, Queen)) => 8
|
||||
case Some(Piece(Black, Queen)) => 9
|
||||
case Some(Piece(White, King)) => 10
|
||||
case Some(Piece(Black, King)) => if position.color.white then 11 else 15
|
||||
case None => 0 // unreachable
|
||||
|
||||
val it = occupied.iterator
|
||||
while it.hasNext
|
||||
do writeNibbles(builder, packPiece(it.next), if it.hasNext then packPiece(it.next) else 0)
|
||||
|
||||
val halfMoveClock = position.history.halfMoveClock.value
|
||||
val ply = input.fullMoveNumber.ply(position.color).value
|
||||
val brokenTurn = position.color.black && position.board.byPiece(Black, King).isEmpty
|
||||
val ply = input.fullMoveNumber.ply(position.color).value
|
||||
val brokenTurn = position.color.black && position.board.byPiece(Black, King).isEmpty
|
||||
val variantHeader = position.variant match
|
||||
case Standard => 0
|
||||
case Crazyhouse => 1
|
||||
case Chess960 => 2
|
||||
case FromPosition => 3
|
||||
case Standard => 0
|
||||
case Crazyhouse => 1
|
||||
case Chess960 => 2
|
||||
case FromPosition => 3
|
||||
case KingOfTheHill => 4
|
||||
case ThreeCheck => 5
|
||||
case Antichess => 6
|
||||
case Atomic => 7
|
||||
case Horde => 8
|
||||
case RacingKings => 9
|
||||
case ThreeCheck => 5
|
||||
case Antichess => 6
|
||||
case Atomic => 7
|
||||
case Horde => 8
|
||||
case RacingKings => 9
|
||||
|
||||
if halfMoveClock > 0 || ply > 1 || brokenTurn || variantHeader != 0
|
||||
then writeLeb128(builder, halfMoveClock)
|
||||
@@ -248,7 +248,7 @@ object BinaryFen:
|
||||
writeNibbles(builder, position.history.checkCount.white, position.history.checkCount.black)
|
||||
else if position.variant.crazyhouse then
|
||||
val crazyData = position.crazyData.getOrElse(Crazyhouse.Data.init)
|
||||
val pockets = crazyData.pockets
|
||||
val pockets = crazyData.pockets
|
||||
writeNibbles(builder, pockets.white.pawn, pockets.black.pawn)
|
||||
writeNibbles(builder, pockets.white.knight, pockets.black.knight)
|
||||
writeNibbles(builder, pockets.white.bishop, pockets.black.bishop)
|
||||
@@ -289,7 +289,7 @@ object BinaryFen:
|
||||
builder.addOne(n.toByte)
|
||||
|
||||
def readLeb128(reader: Iterator[Byte]): Int =
|
||||
var n = 0
|
||||
var n = 0
|
||||
var shift = 0
|
||||
while
|
||||
val b = reader.next
|
||||
@@ -307,8 +307,8 @@ object BinaryFen:
|
||||
((b & 0xf), (b >>> 4) & 0xf)
|
||||
|
||||
def minimumUnmovedRooks(position: Position): UnmovedRooks =
|
||||
val white = position.history.unmovedRooks.bb & position.white & Bitboard.firstRank
|
||||
val black = position.history.unmovedRooks.bb & position.black & Bitboard.lastRank
|
||||
val white = position.history.unmovedRooks.bb & position.white & Bitboard.firstRank
|
||||
val black = position.history.unmovedRooks.bb & position.black & Bitboard.lastRank
|
||||
val castles = position.history.castles
|
||||
UnmovedRooks(
|
||||
(if castles.whiteKingSide then white.isolateLast else Bitboard.empty) |
|
||||
@@ -325,8 +325,8 @@ object BinaryFen:
|
||||
): Castles =
|
||||
val whiteRooks = unmovedRooks.bb & white & Bitboard.firstRank
|
||||
val blackRooks = unmovedRooks.bb & black & Bitboard.lastRank
|
||||
val whiteKing = (white & kings & Bitboard.firstRank).first.getOrElse(Square.E1)
|
||||
val blackKing = (black & kings & Bitboard.lastRank).first.getOrElse(Square.E8)
|
||||
val whiteKing = (white & kings & Bitboard.firstRank).first.getOrElse(Square.E1)
|
||||
val blackKing = (black & kings & Bitboard.lastRank).first.getOrElse(Square.E8)
|
||||
Castles(
|
||||
whiteKingSide = whiteRooks.last.exists(r => r.value > whiteKing.value),
|
||||
whiteQueenSide = whiteRooks.first.exists(r => r.value < whiteKing.value),
|
||||
|
||||
@@ -25,11 +25,11 @@ object FullFen extends OpaqueString[FullFen]:
|
||||
|
||||
def isInitial: Boolean = a == initial
|
||||
|
||||
def simple: SimpleFen = SimpleFen.fromFull(a)
|
||||
def simple: SimpleFen = SimpleFen.fromFull(a)
|
||||
def opening: StandardFen = SimpleFen.opening(a)
|
||||
def board: BoardFen = SimpleFen.board(a)
|
||||
def board: BoardFen = SimpleFen.board(a)
|
||||
|
||||
val initial: FullFen = FullFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
|
||||
val initial: FullFen = FullFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
|
||||
def clean(source: String): FullFen = FullFen(source.replace("_", " ").trim)
|
||||
def getColor(arr: Array[String]): Option[Color] =
|
||||
if arr.length < 2 then None
|
||||
@@ -41,23 +41,23 @@ object FullFen extends OpaqueString[FullFen]:
|
||||
opaque type SimpleFen = String
|
||||
object SimpleFen extends OpaqueString[SimpleFen]:
|
||||
extension (a: SimpleFen)
|
||||
def color: Option[Color] = a.split(' ').lift(1).flatMap(_.headOption).flatMap(Color.apply)
|
||||
def colorOrWhite: Color = color | Color.White
|
||||
def castling: String = a.split(' ').lift(2) | "-"
|
||||
def color: Option[Color] = a.split(' ').lift(1).flatMap(_.headOption).flatMap(Color.apply)
|
||||
def colorOrWhite: Color = color | Color.White
|
||||
def castling: String = a.split(' ').lift(2) | "-"
|
||||
def enpassant: Option[Square] = a.split(' ').lift(3).flatMap(Square.fromKey(_))
|
||||
def opening: StandardFen = StandardFen.fromSimple(a)
|
||||
def board: BoardFen = a.takeWhile(_ != ' ')
|
||||
def opening: StandardFen = StandardFen.fromSimple(a)
|
||||
def board: BoardFen = a.takeWhile(_ != ' ')
|
||||
def fromFull(fen: FullFen): StandardFen =
|
||||
fen.value.split(' ').take(4) match
|
||||
case Array(board, turn, castle, ep) => SimpleFen(s"$board $turn $castle $ep")
|
||||
case _ => fen.into(SimpleFen)
|
||||
case _ => fen.into(SimpleFen)
|
||||
|
||||
// Like SimpleFen, but for standard chess, without ZH pockets
|
||||
opaque type StandardFen = String
|
||||
object StandardFen extends OpaqueString[StandardFen]:
|
||||
extension (a: StandardFen) def board: BoardFen = a.value.takeWhile(_ != ' ')
|
||||
def fromFull(fen: FullFen): StandardFen = fromSimple(FullFen.simple(fen))
|
||||
def fromSimple(fen: SimpleFen): StandardFen =
|
||||
def fromFull(fen: FullFen): StandardFen = fromSimple(FullFen.simple(fen))
|
||||
def fromSimple(fen: SimpleFen): StandardFen =
|
||||
fen.value.split(' ').take(4) match
|
||||
case Array(board, turn, castle, ep) =>
|
||||
StandardFen(s"${BoardFen(board).removePockets} $turn $castle $ep")
|
||||
@@ -72,7 +72,7 @@ object BoardAndColorFen extends OpaqueString[BoardAndColorFen]
|
||||
opaque type BoardFen = String
|
||||
object BoardFen extends OpaqueString[BoardFen]:
|
||||
extension (a: BoardFen)
|
||||
def andColor(c: Color) = BoardAndColorFen(s"$a ${c.letter}")
|
||||
def andColor(c: Color) = BoardAndColorFen(s"$a ${c.letter}")
|
||||
def removePockets: BoardFen =
|
||||
if a.contains('[') then a.takeWhile('[' !=)
|
||||
else if a.count('/' == _) == 8 then a.split('/').take(8).mkString("/")
|
||||
|
||||
@@ -20,7 +20,7 @@ trait FenReader:
|
||||
makeBoard(variant, fBoard).map { (board, crazyData) =>
|
||||
// We trust Fen's color to be correct, if there is no color we use the color of the king in check
|
||||
// If there is no king in check we use white
|
||||
val color = fColor.orElse(variant.checkColor(board)) | Color.White
|
||||
val color = fColor.orElse(variant.checkColor(board)) | Color.White
|
||||
val position = new Position(
|
||||
board,
|
||||
History(
|
||||
@@ -36,7 +36,7 @@ trait FenReader:
|
||||
else
|
||||
fCastling.foldLeft(Castles.none -> UnmovedRooks.none):
|
||||
case ((c, r), ch) =>
|
||||
val color = Color.fromWhite(ch.isUpper)
|
||||
val color = Color.fromWhite(ch.isUpper)
|
||||
val backRank = Bitboard.rank(color.backRank)
|
||||
// rooks that can be used for castling
|
||||
val rooks = position.rooks & position.byColor(color) & backRank
|
||||
@@ -44,8 +44,8 @@ trait FenReader:
|
||||
for
|
||||
kingSquare <- (position.kingOf(color) & backRank).first
|
||||
rookSquare <- ch.toLower match
|
||||
case 'k' => rooks.findLast(_ ?> kingSquare)
|
||||
case 'q' => rooks.find(_ ?< kingSquare)
|
||||
case 'k' => rooks.findLast(_ ?> kingSquare)
|
||||
case 'q' => rooks.find(_ ?< kingSquare)
|
||||
case file => rooks.find(_.file.char == file)
|
||||
side <- Side.kingRookSide(kingSquare, rookSquare)
|
||||
yield (c.add(color, side), r | rookSquare.bl)
|
||||
@@ -149,33 +149,33 @@ trait FenReader:
|
||||
|
||||
def makeBoardWithCrazyPromoted(boardFen: String): (BoardWithCrazyPromoted, Option[String]) =
|
||||
var promoted = Bitboard.empty
|
||||
var pawns = Bitboard.empty
|
||||
var knights = Bitboard.empty
|
||||
var bishops = Bitboard.empty
|
||||
var rooks = Bitboard.empty
|
||||
var queens = Bitboard.empty
|
||||
var kings = Bitboard.empty
|
||||
var white = Bitboard.empty
|
||||
var black = Bitboard.empty
|
||||
var pawns = Bitboard.empty
|
||||
var knights = Bitboard.empty
|
||||
var bishops = Bitboard.empty
|
||||
var rooks = Bitboard.empty
|
||||
var queens = Bitboard.empty
|
||||
var kings = Bitboard.empty
|
||||
var white = Bitboard.empty
|
||||
var black = Bitboard.empty
|
||||
var occupied = Bitboard.empty
|
||||
|
||||
inline def addPieceAt(p: Piece, s: Long) =
|
||||
occupied |= s
|
||||
p.role match
|
||||
case Pawn => pawns |= s
|
||||
case Pawn => pawns |= s
|
||||
case Knight => knights |= s
|
||||
case Bishop => bishops |= s
|
||||
case Rook => rooks |= s
|
||||
case Queen => queens |= s
|
||||
case King => kings |= s
|
||||
case Rook => rooks |= s
|
||||
case Queen => queens |= s
|
||||
case King => kings |= s
|
||||
|
||||
p.color match
|
||||
case Color.White => white |= s
|
||||
case Color.Black => black |= s
|
||||
|
||||
var rank = 7
|
||||
var file = 0
|
||||
val iter = boardFen.iterator.buffered
|
||||
var rank = 7
|
||||
var file = 0
|
||||
val iter = boardFen.iterator.buffered
|
||||
var error = none[String]
|
||||
while iter.hasNext && error.isEmpty
|
||||
do
|
||||
@@ -185,7 +185,7 @@ trait FenReader:
|
||||
if rank < 0 then error = Some("too many ranks")
|
||||
else
|
||||
iter.next match
|
||||
case '/' => // ignored, optional. Rank switch is automatic
|
||||
case '/' => // ignored, optional. Rank switch is automatic
|
||||
case ch if numberSet.contains(ch) =>
|
||||
file += (ch - '0')
|
||||
if file > 8 then error = Some(s"file = $file")
|
||||
|
||||
@@ -12,7 +12,7 @@ import chess.variant.Crazyhouse
|
||||
trait FenWriter:
|
||||
|
||||
private given Ordering[File] = Ordering.by[File, Int](_.value)
|
||||
given Ordering[Square] = Ordering.by[Square, File](_.file)
|
||||
given Ordering[Square] = Ordering.by[Square, File](_.file)
|
||||
|
||||
def write(position: Position): FullFen =
|
||||
write(position, FullMoveNumber(1))
|
||||
@@ -47,13 +47,13 @@ trait FenWriter:
|
||||
.fold("-")(_.key)}"
|
||||
|
||||
def writeBoard(position: Position): BoardFen =
|
||||
val fen = scala.collection.mutable.StringBuilder(70)
|
||||
val fen = scala.collection.mutable.StringBuilder(70)
|
||||
var empty = 0
|
||||
for y <- Rank.allReversed do
|
||||
empty = 0
|
||||
for x <- File.all do
|
||||
position.pieceAt(x, y) match
|
||||
case None => empty = empty + 1
|
||||
case None => empty = empty + 1
|
||||
case Some(piece) =>
|
||||
if empty == 0 then fen.append(piece.forsyth.toString)
|
||||
else
|
||||
@@ -81,8 +81,8 @@ trait FenWriter:
|
||||
case _ => ""
|
||||
|
||||
private[chess] def writeCastles(position: Position): String =
|
||||
val wr = position.rooks & position.white & Bitboard.rank(White.backRank)
|
||||
val br = position.rooks & position.black & Bitboard.rank(Black.backRank)
|
||||
val wr = position.rooks & position.white & Bitboard.rank(White.backRank)
|
||||
val br = position.rooks & position.black & Bitboard.rank(Black.backRank)
|
||||
val wur = position.unmovedRooks.without(Black).bb
|
||||
val bur = position.unmovedRooks.without(White).bb
|
||||
(if position.castles.whiteKingSide then
|
||||
@@ -106,4 +106,4 @@ trait FenWriter:
|
||||
.getOrElse("q")
|
||||
else "") match
|
||||
case "" => "-"
|
||||
case s => s
|
||||
case s => s
|
||||
|
||||
@@ -21,10 +21,10 @@ object Uci:
|
||||
) extends Uci:
|
||||
|
||||
def keys: String = s"${orig.key}${dest.key}"
|
||||
def uci: String = s"$keys$promotionString"
|
||||
def uci: String = s"$keys$promotionString"
|
||||
|
||||
def charKeys: String = s"${orig.asChar}${dest.asChar}"
|
||||
def chars: String = s"$charKeys$promotionString"
|
||||
def chars: String = s"$charKeys$promotionString"
|
||||
|
||||
def promotionString: String = promotion.fold("")(_.forsyth.toString)
|
||||
|
||||
@@ -67,12 +67,12 @@ object Uci:
|
||||
object Drop:
|
||||
|
||||
def fromChars(move: String): Option[Drop] = for
|
||||
role <- move.headOption.flatMap(Role.allByPgn.get)
|
||||
role <- move.headOption.flatMap(Role.allByPgn.get)
|
||||
square <- move.lift(2).flatMap(Square.fromChar)
|
||||
yield Uci.Drop(role, square)
|
||||
|
||||
def fromStrings(roleS: String, posS: String): Option[Drop] = for
|
||||
role <- Role.allByName.get(roleS)
|
||||
role <- Role.allByName.get(roleS)
|
||||
square <- Square.fromKey(posS)
|
||||
yield Drop(role, square)
|
||||
|
||||
@@ -85,7 +85,7 @@ object Uci:
|
||||
def apply(move: String): Option[Uci] =
|
||||
if move.lift(1).contains('@') then
|
||||
for
|
||||
role <- move.headOption.flatMap(Role.allByPgn.get)
|
||||
role <- move.headOption.flatMap(Role.allByPgn.get)
|
||||
square <- Square.fromKey(move.slice(2, 4))
|
||||
yield Uci.Drop(role, square)
|
||||
else Uci.Move(move)
|
||||
|
||||
@@ -13,10 +13,10 @@ case class UciCharPair(a: Char, b: Char):
|
||||
|
||||
char2squareMap.get(b) match
|
||||
case Some(sq) => Uci.Move(from, sq, None)
|
||||
case None =>
|
||||
case None =>
|
||||
char2promotionMap.get(b) match
|
||||
case Some((file, prom)) => Uci.Move(from, Square(file, lastRank(from)), Some(prom))
|
||||
case None => Uci.Drop(unsafeCharToDropRole(b), from)
|
||||
case None => Uci.Drop(unsafeCharToDropRole(b), from)
|
||||
|
||||
object UciCharPair:
|
||||
|
||||
@@ -24,14 +24,14 @@ object UciCharPair:
|
||||
|
||||
def apply(uci: Uci): UciCharPair =
|
||||
uci match
|
||||
case Uci.Move(orig, dest, None) => UciCharPair(toChar(orig), toChar(dest))
|
||||
case Uci.Move(orig, dest, None) => UciCharPair(toChar(orig), toChar(dest))
|
||||
case Uci.Move(orig, dest, Some(role)) => UciCharPair(toChar(orig), toChar(dest.file, role))
|
||||
case Uci.Drop(role, square) => UciCharPair(toChar(square), dropRole2charMap.getOrElse(role, voidChar))
|
||||
|
||||
object implementation:
|
||||
|
||||
val charShift = 35 // Start at Char(35) == '#'
|
||||
val voidChar = 33.toChar // '!'. We skipped Char(34) == '"'.
|
||||
val charShift = 35 // Start at Char(35) == '#'
|
||||
val voidChar = 33.toChar // '!'. We skipped Char(34) == '"'.
|
||||
|
||||
val square2charMap: Map[Square, Char] = Square.all.map { square =>
|
||||
square -> (square.hashCode + charShift).toChar
|
||||
@@ -44,7 +44,7 @@ object UciCharPair:
|
||||
|
||||
val promotion2charMap: Map[(File, PromotableRole), Char] = for
|
||||
(role, index) <- Role.allPromotable.zipWithIndex.toMap
|
||||
file <- File.all
|
||||
file <- File.all
|
||||
yield (file, role) -> (charShift + square2charMap.size + index * 8 + file.value).toChar
|
||||
|
||||
lazy val char2promotionMap: Map[Char, (File, PromotableRole)] =
|
||||
|
||||
@@ -5,13 +5,13 @@ package format
|
||||
* Made from concatenated UciCharPair strings */
|
||||
opaque type UciPath = String
|
||||
object UciPath extends OpaqueString[UciPath]:
|
||||
def fromId(id: UciCharPair): UciPath = id.toString
|
||||
def fromId(id: UciCharPair): UciPath = id.toString
|
||||
def fromIds(ids: Iterable[UciCharPair]): UciPath = ids.mkString
|
||||
|
||||
extension (e: UciPath)
|
||||
|
||||
def computeIds: Iterator[UciCharPair] = e.grouped(2).flatMap(strToId)
|
||||
def ids: List[UciCharPair] = computeIds.toList
|
||||
def ids: List[UciCharPair] = computeIds.toList
|
||||
|
||||
def head: Option[UciCharPair] = strToId(e)
|
||||
|
||||
@@ -19,13 +19,13 @@ object UciPath extends OpaqueString[UciPath]:
|
||||
|
||||
def split: Option[(UciCharPair, UciPath)] = head.map(_ -> e.drop(2))
|
||||
|
||||
inline def isEmpty: Boolean = e.isEmpty
|
||||
inline def isEmpty: Boolean = e.isEmpty
|
||||
inline def nonEmpty: Boolean = !isEmpty
|
||||
|
||||
def lastId: Option[UciCharPair] = strToId(e.takeRight(2))
|
||||
|
||||
def +(id: UciCharPair): UciPath = e + id.toString
|
||||
def +(more: UciPath): UciPath = e + more
|
||||
def +(more: UciPath): UciPath = e + more
|
||||
|
||||
def prepend(id: UciCharPair): UciPath = id.toString + e
|
||||
|
||||
|
||||
@@ -5,28 +5,28 @@ import scala.util.Try
|
||||
|
||||
object Binary:
|
||||
|
||||
def writeMove(m: SanStr): Try[List[Byte]] = Try(Writer.move(m))
|
||||
def writeMove(m: SanStr): Try[List[Byte]] = Try(Writer.move(m))
|
||||
def writeMoves(ms: Iterable[SanStr]): Try[Array[Byte]] = Try(Writer.moves(ms))
|
||||
|
||||
def readMoves(bs: List[Byte]): Try[List[SanStr]] = Try(Reader.moves(bs))
|
||||
def readMoves(bs: List[Byte]): Try[List[SanStr]] = Try(Reader.moves(bs))
|
||||
def readMoves(bs: List[Byte], nb: Int): Try[List[SanStr]] = Try(Reader.moves(bs, nb))
|
||||
|
||||
private object MoveType:
|
||||
val SimplePawn = 0
|
||||
val SimplePawn = 0
|
||||
val SimplePiece = 1
|
||||
val FullPawn = 2
|
||||
val FullPiece = 3
|
||||
val FullPawn = 2
|
||||
val FullPiece = 3
|
||||
|
||||
private object Encoding:
|
||||
val pieceInts: Map[String, Int] =
|
||||
Map("K" -> 1, "Q" -> 2, "R" -> 3, "N" -> 4, "B" -> 5, "O-O" -> 6, "O-O-O" -> 7)
|
||||
val pieceStrs: Map[Int, String] = pieceInts.map { case (k, v) => v -> k }
|
||||
val pieceStrs: Map[Int, String] = pieceInts.map { case (k, v) => v -> k }
|
||||
val dropPieceInts: Map[String, Int] = Map("P" -> 1, "Q" -> 2, "R" -> 3, "N" -> 4, "B" -> 5)
|
||||
val dropPieceStrs: Map[Int, String] = dropPieceInts.map { case (k, v) => v -> k }
|
||||
val promotionInts: Map[String, Int] = Map("" -> 0, "Q" -> 1, "R" -> 2, "N" -> 3, "B" -> 4, "K" -> 6)
|
||||
val promotionStrs: Map[Int, String] = promotionInts.map { case (k, v) => v -> k }
|
||||
val checkInts: Map[String, Int] = Map("" -> 0, "+" -> 1, "#" -> 2)
|
||||
val checkStrs: Map[Int, String] = checkInts.map { case (k, v) => v -> k }
|
||||
val checkInts: Map[String, Int] = Map("" -> 0, "+" -> 1, "#" -> 2)
|
||||
val checkStrs: Map[Int, String] = checkInts.map { case (k, v) => v -> k }
|
||||
|
||||
private object Reader:
|
||||
|
||||
@@ -34,13 +34,13 @@ object Binary:
|
||||
|
||||
private val maxPlies = 600
|
||||
|
||||
def moves(bs: List[Byte]): List[SanStr] = moves(bs, maxPlies)
|
||||
def moves(bs: List[Byte]): List[SanStr] = moves(bs, maxPlies)
|
||||
def moves(bs: List[Byte], nb: Int): List[SanStr] = SanStr.from(intMoves(bs.map(Binary.toInt(_)), nb))
|
||||
|
||||
def intMoves(bs: List[Int], pliesToGo: Int): List[String] =
|
||||
bs match
|
||||
case _ if pliesToGo <= 0 => Nil
|
||||
case Nil => Nil
|
||||
case _ if pliesToGo <= 0 => Nil
|
||||
case Nil => Nil
|
||||
case b1 :: rest if moveType(b1) == MoveType.SimplePawn =>
|
||||
simplePawn(b1) :: intMoves(rest, pliesToGo - 1)
|
||||
case b1 :: b2 :: rest if moveType(b1) == MoveType.SimplePiece =>
|
||||
@@ -69,47 +69,47 @@ object Binary:
|
||||
val check = checkStrs(cut(b2, 5, 3))
|
||||
s"$castle$check"
|
||||
case piece =>
|
||||
val square = squareString(right(b1, 6))
|
||||
val square = squareString(right(b1, 6))
|
||||
val capture = if bitAt(b2, 3) then "x" else ""
|
||||
val check = checkStrs(cut(b2, 5, 3))
|
||||
val check = checkStrs(cut(b2, 5, 3))
|
||||
s"$piece$capture$square$check"
|
||||
def drop(b1: Int, b2: Int): String =
|
||||
val piece = dropPieceStrs(b2 >> 5)
|
||||
val piece = dropPieceStrs(b2 >> 5)
|
||||
val square = squareString(right(b1, 6))
|
||||
val check = checkStrs(cut(b2, 5, 3))
|
||||
val check = checkStrs(cut(b2, 5, 3))
|
||||
s"$piece@$square$check"
|
||||
|
||||
def fullPawn(b1: Int, b2: Int): String =
|
||||
val square = squareString(right(b1, 6))
|
||||
val square = squareString(right(b1, 6))
|
||||
val fileCapture = (b2 >> 6) match
|
||||
case 1 => s"${(square(0) - 1).toChar}x"
|
||||
case 2 => s"${(square(0) + 1).toChar}x"
|
||||
case _ => ""
|
||||
val check = checkStrs(cut(b2, 6, 4))
|
||||
val prom = promotionStrs(cut(b2, 4, 1))
|
||||
val check = checkStrs(cut(b2, 6, 4))
|
||||
val prom = promotionStrs(cut(b2, 4, 1))
|
||||
val promotion = if prom.isEmpty then "" else s"=$prom"
|
||||
s"$fileCapture$square$promotion$check"
|
||||
|
||||
def fullPiece(b1: Int, b2: Int, b3: Int): String =
|
||||
val square = squareString(right(b1, 6))
|
||||
val piece = pieceStrs(b2 >> 5)
|
||||
val square = squareString(right(b1, 6))
|
||||
val piece = pieceStrs(b2 >> 5)
|
||||
val capture = if bitAt(b2, 3) then "x" else ""
|
||||
val check = checkStrs(cut(b2, 5, 3))
|
||||
val disamb = (b3 >> 6) match
|
||||
val check = checkStrs(cut(b2, 5, 3))
|
||||
val disamb = (b3 >> 6) match
|
||||
case 0 => fileChar(right(b3, 3)).toString
|
||||
case 1 => rankChar(right(b3, 3)).toString
|
||||
case _ => squareString(right(b3, 6))
|
||||
s"$piece$disamb$capture$square$check"
|
||||
|
||||
private def moveType(i: Int) = i >> 6
|
||||
private def moveType(i: Int) = i >> 6
|
||||
private def squareString(i: Int) = fileChar(i >> 3).toString + rankChar(right(i, 3))
|
||||
private def fileChar(i: Int) = (i + 97).toChar
|
||||
private def rankChar(i: Int) = (i + 49).toChar
|
||||
private def fileChar(i: Int) = (i + 97).toChar
|
||||
private def rankChar(i: Int) = (i + 49).toChar
|
||||
|
||||
private def right(i: Int, x: Int): Int = i & lengthMasks(x)
|
||||
private def right(i: Int, x: Int): Int = i & lengthMasks(x)
|
||||
private def cut(i: Int, from: Int, to: Int): Int = right(i, from) >> to
|
||||
private def bitAt(i: Int, p: Int): Boolean = cut(i, p, p - 1) != 0
|
||||
private val lengthMasks =
|
||||
private def bitAt(i: Int, p: Int): Boolean = cut(i, p, p - 1) != 0
|
||||
private val lengthMasks =
|
||||
Map(1 -> 0x01, 2 -> 0x03, 3 -> 0x07, 4 -> 0x0f, 5 -> 0x1f, 6 -> 0x3f, 7 -> 0x7f, 8 -> 0xff)
|
||||
private def !!(msg: String) = throw Exception("Binary reader failed: " + msg)
|
||||
|
||||
@@ -119,8 +119,8 @@ object Binary:
|
||||
|
||||
def move(str: SanStr): List[Byte] =
|
||||
(str.value match
|
||||
case square if square.length == 2 => simplePawn(square)
|
||||
case CastlingR(str, check) => castling(str, check)
|
||||
case square if square.length == 2 => simplePawn(square)
|
||||
case CastlingR(str, check) => castling(str, check)
|
||||
case SimplePieceR(piece, capture, square, check) =>
|
||||
simplePiece(piece, square, capture, check)
|
||||
case FullPawnR(file, square, promotion, check) =>
|
||||
@@ -178,7 +178,7 @@ object Binary:
|
||||
else if orig.head.toInt < 97 then rankInt(orig.head)
|
||||
else fileInt(orig.head)
|
||||
|
||||
def boolInt(s: String): Int = if s.nonEmpty then 1 else 0
|
||||
def boolInt(s: String): Int = if s.nonEmpty then 1 else 0
|
||||
def boolInt(b: Boolean): Int = if b then 1 else 0
|
||||
|
||||
def posInt(square: String): Int = posInt(fileInt(square.head), rankInt(square(1)))
|
||||
@@ -192,18 +192,18 @@ object Binary:
|
||||
if file.head < square.head then 1 else 2
|
||||
}
|
||||
|
||||
val pieceR = "([KQRNB])"
|
||||
val fileR = "(?:([a-h])x)?"
|
||||
val posR = "([a-h][1-9])"
|
||||
val captureR = "(x?)"
|
||||
val checkR = "([\\+#]?)"
|
||||
val promotionR = "(?:\\=?([QRNBK]))?"
|
||||
val origR = "([a-h]?[1-8]?)".r
|
||||
val pieceR = "([KQRNB])"
|
||||
val fileR = "(?:([a-h])x)?"
|
||||
val posR = "([a-h][1-9])"
|
||||
val captureR = "(x?)"
|
||||
val checkR = "([\\+#]?)"
|
||||
val promotionR = "(?:\\=?([QRNBK]))?"
|
||||
val origR = "([a-h]?[1-8]?)".r
|
||||
val SimplePieceR = s"^$pieceR$captureR$posR$checkR$$".r
|
||||
val FullPawnR = s"^$fileR$posR$promotionR$checkR$$".r
|
||||
val CastlingR = s"^(O-O|O-O-O)$checkR$$".r
|
||||
val FullPieceR = s"^$pieceR$origR$captureR$posR$checkR$$".r
|
||||
val DropR = s"^([QRNBP])@$posR$checkR$$".r
|
||||
val FullPawnR = s"^$fileR$posR$promotionR$checkR$$".r
|
||||
val CastlingR = s"^(O-O|O-O-O)$checkR$$".r
|
||||
val FullPieceR = s"^$pieceR$origR$captureR$posR$checkR$$".r
|
||||
val DropR = s"^([QRNBP])@$posR$checkR$$".r
|
||||
|
||||
private inline def toInt(b: Byte): Int = b & 0xff
|
||||
private def showByte(b: Int): String = "%08d".format(b.toBinaryString.toInt)
|
||||
private def showByte(b: Int): String = "%08d".format(b.toBinaryString.toInt)
|
||||
|
||||
@@ -19,9 +19,9 @@ case class Glyphs(
|
||||
|
||||
def toggle(glyph: Glyph): Glyphs =
|
||||
glyph match
|
||||
case g: Glyph.MoveAssessment => copy(move = (!move.contains(g)).option(g))
|
||||
case g: Glyph.MoveAssessment => copy(move = (!move.contains(g)).option(g))
|
||||
case g: Glyph.PositionAssessment => copy(position = (!position.contains(g)).option(g))
|
||||
case g: Glyph.Observation =>
|
||||
case g: Glyph.Observation =>
|
||||
copy(observations =
|
||||
if observations.contains(g) then observations.filter(g !=)
|
||||
else g :: observations
|
||||
@@ -62,16 +62,16 @@ object Glyph:
|
||||
sealed trait MoveAssessment extends Glyph
|
||||
|
||||
object MoveAssessment:
|
||||
val good = new Glyph(1, "!", "Good move") with MoveAssessment
|
||||
val mistake = new Glyph(2, "?", "Mistake") with MoveAssessment
|
||||
val brilliant = new Glyph(3, "!!", "Brilliant move") with MoveAssessment
|
||||
val blunder = new Glyph(4, "??", "Blunder") with MoveAssessment
|
||||
val good = new Glyph(1, "!", "Good move") with MoveAssessment
|
||||
val mistake = new Glyph(2, "?", "Mistake") with MoveAssessment
|
||||
val brilliant = new Glyph(3, "!!", "Brilliant move") with MoveAssessment
|
||||
val blunder = new Glyph(4, "??", "Blunder") with MoveAssessment
|
||||
val interesting = new Glyph(5, "!?", "Interesting move") with MoveAssessment
|
||||
val dubious = new Glyph(6, "?!", "Dubious move") with MoveAssessment
|
||||
val only = new Glyph(7, "□", "Only move") with MoveAssessment
|
||||
val zugzwang = new Glyph(22, "⨀", "Zugzwang") with MoveAssessment
|
||||
val dubious = new Glyph(6, "?!", "Dubious move") with MoveAssessment
|
||||
val only = new Glyph(7, "□", "Only move") with MoveAssessment
|
||||
val zugzwang = new Glyph(22, "⨀", "Zugzwang") with MoveAssessment
|
||||
|
||||
val all = List(good, mistake, brilliant, blunder, interesting, dubious, only, zugzwang)
|
||||
val all = List(good, mistake, brilliant, blunder, interesting, dubious, only, zugzwang)
|
||||
val byId: Map[Int, Glyph] = all.mapBy(_.id)
|
||||
|
||||
def display = all
|
||||
@@ -79,14 +79,14 @@ object Glyph:
|
||||
sealed trait PositionAssessment extends Glyph
|
||||
|
||||
object PositionAssessment:
|
||||
val equal = new Glyph(10, "=", "Equal position") with PositionAssessment
|
||||
val unclear = new Glyph(13, "∞", "Unclear position") with PositionAssessment
|
||||
val equal = new Glyph(10, "=", "Equal position") with PositionAssessment
|
||||
val unclear = new Glyph(13, "∞", "Unclear position") with PositionAssessment
|
||||
val whiteSlightlyBetter = new Glyph(14, "⩲", "White is slightly better") with PositionAssessment
|
||||
val blackSlightlyBetter = new Glyph(15, "⩱", "Black is slightly better") with PositionAssessment
|
||||
val whiteQuiteBetter = new Glyph(16, "±", "White is better") with PositionAssessment
|
||||
val blackQuiteBetter = new Glyph(17, "∓", "Black is better") with PositionAssessment
|
||||
val whiteMuchBetter = new Glyph(18, "+−", "White is winning") with PositionAssessment
|
||||
val blackMuchBetter = new Glyph(19, "-+", "Black is winning") with PositionAssessment
|
||||
val whiteQuiteBetter = new Glyph(16, "±", "White is better") with PositionAssessment
|
||||
val blackQuiteBetter = new Glyph(17, "∓", "Black is better") with PositionAssessment
|
||||
val whiteMuchBetter = new Glyph(18, "+−", "White is winning") with PositionAssessment
|
||||
val blackMuchBetter = new Glyph(19, "-+", "Black is winning") with PositionAssessment
|
||||
|
||||
val all = List(
|
||||
equal,
|
||||
@@ -105,14 +105,14 @@ object Glyph:
|
||||
sealed trait Observation extends Glyph
|
||||
|
||||
object Observation:
|
||||
val novelty = new Glyph(146, "N", "Novelty") with Observation
|
||||
val development = new Glyph(32, "↑↑", "Development") with Observation
|
||||
val initiative = new Glyph(36, "↑", "Initiative") with Observation
|
||||
val attack = new Glyph(40, "→", "Attack") with Observation
|
||||
val counterplay = new Glyph(132, "⇆", "Counterplay") with Observation
|
||||
val timeTrouble = new Glyph(138, "⊕", "Time trouble") with Observation
|
||||
val novelty = new Glyph(146, "N", "Novelty") with Observation
|
||||
val development = new Glyph(32, "↑↑", "Development") with Observation
|
||||
val initiative = new Glyph(36, "↑", "Initiative") with Observation
|
||||
val attack = new Glyph(40, "→", "Attack") with Observation
|
||||
val counterplay = new Glyph(132, "⇆", "Counterplay") with Observation
|
||||
val timeTrouble = new Glyph(138, "⊕", "Time trouble") with Observation
|
||||
val compensation = new Glyph(44, "=∞", "With compensation") with Observation
|
||||
val withIdea = new Glyph(140, "∆", "With the idea") with Observation
|
||||
val withIdea = new Glyph(140, "∆", "With the idea") with Observation
|
||||
|
||||
val all = List(novelty, development, initiative, attack, counterplay, timeTrouble, compensation, withIdea)
|
||||
val byId: Map[Int, Glyph] = all.mapBy(_.id)
|
||||
|
||||
@@ -10,9 +10,9 @@ import cats.syntax.all.*
|
||||
object Parser:
|
||||
|
||||
// https://unicode-explorer.com/c/00A0
|
||||
private val nbsp = P.char('\u00A0')
|
||||
private val nbsp = P.char('\u00A0')
|
||||
private val whitespace = R.cr | R.lf | R.wsp | nbsp | P.char('\uFEFF')
|
||||
val pgnComment = P.caret.filter(_.col == 0) *> P.char('%') *> P.until(P.char('\n')).void
|
||||
val pgnComment = P.caret.filter(_.col == 0) *> P.char('%') *> P.until(P.char('\n')).void
|
||||
// pgnComment with % or whitespaces
|
||||
private val escape = pgnComment.? *> whitespace.rep0.?
|
||||
|
||||
@@ -54,9 +54,9 @@ object Parser:
|
||||
def san(str: SanStr): Either[ErrorStr, San] =
|
||||
simpleSan.parse(str.value, "Error parsing move")
|
||||
|
||||
private val blockComment = P.until0(P.char('}')).with1.between(P.char('{'), P.char('}')).map(Comment(_))
|
||||
private val blockComment = P.until0(P.char('}')).with1.between(P.char('{'), P.char('}')).map(Comment(_))
|
||||
private val inlineComment = P.char(';') *> P.until(R.lf).map(Comment(_))
|
||||
private val comment = (blockComment | inlineComment).withContext("Invalid comment") <* escape
|
||||
private val comment = (blockComment | inlineComment).withContext("Invalid comment") <* escape
|
||||
|
||||
private def mapResult(result: String): String = Outcome.fromResult(result).fold(result)(_.toString)
|
||||
|
||||
@@ -84,8 +84,8 @@ object Parser:
|
||||
.?
|
||||
.flatMap(o => o.fold(P.unit)(_ => P.failWith("Null moves are not supported").void))
|
||||
|
||||
private val preMoveEscape = ((number.backtrack | comment).rep0 ~ forbidNullMove).void
|
||||
private val moveAndMetas = SanParser.san ~ SanParser.metas
|
||||
private val preMoveEscape = ((number.backtrack | comment).rep0 ~ forbidNullMove).void
|
||||
private val moveAndMetas = SanParser.san ~ SanParser.metas
|
||||
private val postMoveEscape = moveExtras.rep0.void <* escape
|
||||
|
||||
private val simpleSan: P[San] =
|
||||
@@ -112,7 +112,7 @@ object Parser:
|
||||
(P.char('(') *> comment.rep0.surroundedBy(escape) ~ recurse.rep0 <* (P.char(')') ~ escape))
|
||||
.map((comments, sans) =>
|
||||
sans match
|
||||
case Nil => None
|
||||
case Nil => None
|
||||
case x :: xs =>
|
||||
Variation(x.value.copy(variationComments = comments.cleanUp), Tree.build(xs)).some
|
||||
)
|
||||
@@ -135,7 +135,7 @@ object Parser:
|
||||
val rankMap = Rank.all.mapBy(_.char)
|
||||
|
||||
val glyph: P[Glyph] = mapParser(Glyph.MoveAssessment.all.mapBy(_.symbol), "glyph")
|
||||
val glyphs = glyph.rep0.map(Glyphs.fromList)
|
||||
val glyphs = glyph.rep0.map(Glyphs.fromList)
|
||||
|
||||
val capture = P.char('x').?.map(_.isDefined)
|
||||
|
||||
@@ -194,7 +194,7 @@ object Parser:
|
||||
val castleQSide = List("O-O-O", "o-o-o", "0-0-0", "O‑O‑O", "o‑o‑o", "0‑0‑0", "O–O–O", "o–o–o", "0–0–0")
|
||||
val qCastle: P[Side] = P.stringIn(castleQSide).as(QueenSide)
|
||||
|
||||
val castleKSide = List("O-O", "o-o", "0-0", "O‑O", "o‑o", "0‑0", "O–O", "o–o", "0–0")
|
||||
val castleKSide = List("O-O", "o-o", "0-0", "O‑O", "o‑o", "0‑0", "O–O", "o–o", "0–0")
|
||||
val kCastle: P[Side] = P.stringIn(castleKSide).as(KingSide)
|
||||
|
||||
val castle: P[San] = (qCastle | kCastle).withString.map((side, raw) => Castle(side, raw.some))
|
||||
@@ -222,17 +222,17 @@ object Parser:
|
||||
val tagName: P[String] = R.alpha.rep.string.withContext("Tag name can only contains alphabet characters")
|
||||
val escaped: P[String] = P.char('\\') *> (R.dquote | P.char('\\')).string
|
||||
val valueChar: P[String] = escaped | P.charWhere(_ != '"').string
|
||||
val tagValue: P[String] = valueChar.rep0.map(_.mkString).with1.surroundedBy(R.dquote)
|
||||
val tagContent: P[Tag] = ((tagName <* R.wsp.rep) ~ tagValue).map(Tag(_, _))
|
||||
val tag: P[Tag] = tagContent.between(P.char('['), P.char(']')) <* whitespace.rep0
|
||||
val tags: P0[List[Tag]] = tag.rep0
|
||||
val tagValue: P[String] = valueChar.rep0.map(_.mkString).with1.surroundedBy(R.dquote)
|
||||
val tagContent: P[Tag] = ((tagName <* R.wsp.rep) ~ tagValue).map(Tag(_, _))
|
||||
val tag: P[Tag] = tagContent.between(P.char('['), P.char(']')) <* whitespace.rep0
|
||||
val tags: P0[List[Tag]] = tag.rep0
|
||||
|
||||
private val tagsAndMovesParser: P0[ParsedPgn] =
|
||||
(TagParser.tags.surroundedBy(escape) ~ fullMovesParser.?)
|
||||
.map: (optionalTags, optionalMoves) =>
|
||||
val preTags = Tags.sanitize(optionalTags)
|
||||
optionalMoves match
|
||||
case None => ParsedPgn(InitialComments.empty, preTags, None)
|
||||
case None => ParsedPgn(InitialComments.empty, preTags, None)
|
||||
case Some((init, nodes, result)) =>
|
||||
ParsedPgn(init, updateTagsWithResult(preTags, result), Tree.build(nodes))
|
||||
|
||||
@@ -242,7 +242,7 @@ object Parser:
|
||||
(TagParser.tags.surroundedBy(escape) ~ p.?).map: (optionalTags, optionalMoves) =>
|
||||
val preTags = Tags.sanitize(optionalTags)
|
||||
optionalMoves match
|
||||
case None => ParsedMainline(InitialComments.empty, preTags, Nil)
|
||||
case None => ParsedMainline(InitialComments.empty, preTags, Nil)
|
||||
case Some((init, sans, result)) =>
|
||||
ParsedMainline(init, updateTagsWithResult(preTags, result), sans)
|
||||
|
||||
@@ -252,7 +252,7 @@ object Parser:
|
||||
private inline def escapePgnTag[A](p: P0[A]): P0[A] =
|
||||
escape *> P.string("[pgn]").? *> p <* P.string("[/pgn]").? <* escape
|
||||
|
||||
private val tagsParser = TagParser.tags.surroundedBy(escape)
|
||||
private val tagsParser = TagParser.tags.surroundedBy(escape)
|
||||
private val pgnParser: P0[ParsedPgn] = escapePgnTag(tagsAndMovesParser)
|
||||
|
||||
private val pgnMainlineParser: P0[ParsedMainline[SanWithMetas]] =
|
||||
@@ -266,8 +266,8 @@ object Parser:
|
||||
p.parse(str).bimap(showExpectations(context, str), _._2)
|
||||
|
||||
private def showExpectations(context: String, str: String)(error: P.Error): ErrorStr =
|
||||
val lm = LocationMap(str)
|
||||
val idx = error.failedAtOffset
|
||||
val lm = LocationMap(str)
|
||||
val idx = error.failedAtOffset
|
||||
val caret = lm.toCaret(idx).getOrElse(throw RuntimeException("This is impossible"))
|
||||
ErrorStr:
|
||||
s"$context: ${expToString(error.expected.head)} at line ${caret.line + 1}, column ${caret.col + 1}"
|
||||
@@ -277,16 +277,16 @@ object Parser:
|
||||
case Expectation.OneOfStr(_, strs) =>
|
||||
strs match
|
||||
case one :: Nil => s"expected: $one"
|
||||
case _ => s"expected one of: $strs"
|
||||
case _ => s"expected one of: $strs"
|
||||
case Expectation.InRange(_, lower, upper) =>
|
||||
if lower == upper then s"expected: $lower"
|
||||
else s"expected char in range: [$lower, $upper]"
|
||||
case Expectation.StartOfString(_) => "expected start of the file"
|
||||
case Expectation.EndOfString(_, length) => s"expected end of file but $length characters remaining"
|
||||
case Expectation.StartOfString(_) => "expected start of the file"
|
||||
case Expectation.EndOfString(_, length) => s"expected end of file but $length characters remaining"
|
||||
case Expectation.Length(_, expected, actual) =>
|
||||
s"expected $expected more characters but only $actual remaining"
|
||||
case Expectation.ExpectedFailureAt(_, matched) =>
|
||||
s"expected failure but the parser matched: $matched"
|
||||
case Expectation.Fail(_) => "Failed"
|
||||
case Expectation.FailWith(_, message) => message
|
||||
case Expectation.Fail(_) => "Failed"
|
||||
case Expectation.FailWith(_, message) => message
|
||||
case Expectation.WithContext(contextStr, _) => contextStr
|
||||
|
||||
@@ -69,7 +69,7 @@ case class Move(
|
||||
builder.append(san.value)
|
||||
glyphs.toList.foreach:
|
||||
case glyph if glyph.id <= 6 => builder.append(glyph.symbol)
|
||||
case glyph => builder.append(" $").append(glyph.id)
|
||||
case glyph => builder.append(" $").append(glyph.id)
|
||||
if nonEmpty then
|
||||
List(clockString, opening, result).flatten
|
||||
.:::(comments.map(_.map(Move.noDoubleLineBreak)))
|
||||
@@ -84,7 +84,7 @@ object Move:
|
||||
|
||||
given PgnNodeEncoder[Move] with
|
||||
extension (m: Move)
|
||||
def appendSanStr(builder: StringBuilder) = m.appendSanStr(builder)
|
||||
def appendSanStr(builder: StringBuilder) = m.appendSanStr(builder)
|
||||
def appendVariationComment(builder: StringBuilder) =
|
||||
m.variationComments.foreach(x => builder.append(" { ").append(x.value).append(" }"))
|
||||
def hasComment = m.hasComment
|
||||
|
||||
@@ -62,7 +62,7 @@ object PgnNodeEncoder:
|
||||
addTurnNumberPrefix(forceTurnNumber, builder, ply)
|
||||
renderValueAndVariations(builder, ply)
|
||||
tree.child.match
|
||||
case None => ()
|
||||
case None => ()
|
||||
case Some(x) =>
|
||||
builder.addOne(' ')
|
||||
x.render(builder, tree.forceTurnNumber(ply), ply.next)
|
||||
|
||||
@@ -16,9 +16,9 @@ case class Tag(name: TagType, value: String):
|
||||
str.replace("\\", "\\\\").replace("\"", "\\\"")
|
||||
|
||||
trait TagType:
|
||||
lazy val name = toString
|
||||
lazy val name = toString
|
||||
lazy val lowercase = name.toLowerCase
|
||||
val isUnknown = false
|
||||
val isUnknown = false
|
||||
|
||||
case class Tags(value: List[Tag]) extends AnyVal:
|
||||
|
||||
@@ -41,15 +41,15 @@ case class Tags(value: List[Tag]) extends AnyVal:
|
||||
.map(_.toLowerCase)
|
||||
.flatMap:
|
||||
case "chess 960" | "fischerandom" | "fischerrandom" => chess.variant.Chess960.some
|
||||
case "3 check" | "3-check" => chess.variant.ThreeCheck.some
|
||||
case name => chess.variant.Variant.byName(name)
|
||||
case "3 check" | "3-check" => chess.variant.ThreeCheck.some
|
||||
case name => chess.variant.Variant.byName(name)
|
||||
|
||||
def anyDate: Option[String] = apply(_.UTCDate).orElse(apply(_.Date))
|
||||
|
||||
def year: Option[Int] =
|
||||
anyDate.flatMap:
|
||||
case Tags.DateRegex(y, _, _) => y.toIntOption
|
||||
case _ => None
|
||||
case _ => None
|
||||
|
||||
def fen: Option[FullFen] = FullFen.from(apply(_.FEN))
|
||||
|
||||
@@ -88,7 +88,7 @@ case class Tags(value: List[Tag]) extends AnyVal:
|
||||
.flatMap(_.split('.').lift(1))
|
||||
.flatMap(_.toIntOption)
|
||||
|
||||
def names: ByColor[Option[PlayerName]] = ByColor(apply(_.White), apply(_.Black)).map(PlayerName.from(_))
|
||||
def names: ByColor[Option[PlayerName]] = ByColor(apply(_.White), apply(_.Black)).map(PlayerName.from(_))
|
||||
def ratings: ByColor[Option[IntRating]] = ByColor(apply(_.WhiteElo), apply(_.BlackElo)).map: r =>
|
||||
IntRating.from(r.flatMap(_.toIntOption))
|
||||
def titles: ByColor[Option[PlayerTitle]] =
|
||||
@@ -106,8 +106,8 @@ case class Tags(value: List[Tag]) extends AnyVal:
|
||||
yield minutes * 60 + seconds
|
||||
s.split(':').toList match
|
||||
case List(h, m, s) => (h.toIntOption, readMinutesAndSeconds(m, s)).mapN(_ * 3600 + _)
|
||||
case List(m, s) => readMinutesAndSeconds(m, s)
|
||||
case _ => None
|
||||
case List(m, s) => readMinutesAndSeconds(m, s)
|
||||
case _ => None
|
||||
seconds.map(Centis.ofSeconds)
|
||||
|
||||
override def toString = sorted.value.mkString("\n")
|
||||
@@ -141,49 +141,49 @@ object Tag:
|
||||
|
||||
given Eq[Tag] = Eq.fromUniversalEquals
|
||||
|
||||
case object Event extends TagType
|
||||
case object Site extends TagType
|
||||
case object Date extends TagType
|
||||
case object Event extends TagType
|
||||
case object Site extends TagType
|
||||
case object Date extends TagType
|
||||
case object UTCDate extends TagType:
|
||||
val format = DateTimeFormatter.ofPattern("yyyy.MM.dd")
|
||||
case object UTCTime extends TagType:
|
||||
val format = DateTimeFormatter.ofPattern("HH:mm:ss")
|
||||
case object Round extends TagType
|
||||
case object Board extends TagType
|
||||
case object White extends TagType
|
||||
case object Black extends TagType
|
||||
case object TimeControl extends TagType
|
||||
case object WhiteClock extends TagType
|
||||
case object BlackClock extends TagType
|
||||
case object ReferenceTime extends TagType
|
||||
case object WhiteElo extends TagType
|
||||
case object BlackElo extends TagType
|
||||
case object WhiteRatingDiff extends TagType
|
||||
case object BlackRatingDiff extends TagType
|
||||
case object WhiteTitle extends TagType
|
||||
case object BlackTitle extends TagType
|
||||
case object WhiteTeam extends TagType
|
||||
case object BlackTeam extends TagType
|
||||
case object WhiteFideId extends TagType
|
||||
case object BlackFideId extends TagType
|
||||
case object Result extends TagType
|
||||
case object FEN extends TagType
|
||||
case object Variant extends TagType
|
||||
case object ECO extends TagType
|
||||
case object Opening extends TagType
|
||||
case object Termination extends TagType
|
||||
case object Annotator extends TagType
|
||||
case object GameId extends TagType
|
||||
case object Round extends TagType
|
||||
case object Board extends TagType
|
||||
case object White extends TagType
|
||||
case object Black extends TagType
|
||||
case object TimeControl extends TagType
|
||||
case object WhiteClock extends TagType
|
||||
case object BlackClock extends TagType
|
||||
case object ReferenceTime extends TagType
|
||||
case object WhiteElo extends TagType
|
||||
case object BlackElo extends TagType
|
||||
case object WhiteRatingDiff extends TagType
|
||||
case object BlackRatingDiff extends TagType
|
||||
case object WhiteTitle extends TagType
|
||||
case object BlackTitle extends TagType
|
||||
case object WhiteTeam extends TagType
|
||||
case object BlackTeam extends TagType
|
||||
case object WhiteFideId extends TagType
|
||||
case object BlackFideId extends TagType
|
||||
case object Result extends TagType
|
||||
case object FEN extends TagType
|
||||
case object Variant extends TagType
|
||||
case object ECO extends TagType
|
||||
case object Opening extends TagType
|
||||
case object Termination extends TagType
|
||||
case object Annotator extends TagType
|
||||
case object GameId extends TagType
|
||||
case class Unknown(n: String) extends TagType:
|
||||
override def toString = n
|
||||
override def toString = n
|
||||
override val isUnknown = true
|
||||
|
||||
val names = ByColor(White, Black)
|
||||
val elos = ByColor(WhiteElo, BlackElo)
|
||||
val titles = ByColor(WhiteTitle, BlackTitle)
|
||||
val names = ByColor(White, Black)
|
||||
val elos = ByColor(WhiteElo, BlackElo)
|
||||
val titles = ByColor(WhiteTitle, BlackTitle)
|
||||
val fideIds = ByColor(WhiteFideId, BlackFideId)
|
||||
val teams = ByColor(WhiteTeam, BlackTeam)
|
||||
val clocks = ByColor(WhiteClock, BlackClock)
|
||||
val teams = ByColor(WhiteTeam, BlackTeam)
|
||||
val clocks = ByColor(WhiteClock, BlackClock)
|
||||
|
||||
val tagTypes = List(
|
||||
Event,
|
||||
|
||||
@@ -9,18 +9,18 @@ object FullMoveNumber extends RichOpaqueInt[FullMoveNumber]:
|
||||
val initial: FullMoveNumber = 1
|
||||
extension (e: FullMoveNumber)
|
||||
def ply(turn: Color): Ply = Ply(e * 2 - turn.fold(2, 1))
|
||||
def next: FullMoveNumber = e + 1
|
||||
def next: FullMoveNumber = e + 1
|
||||
|
||||
opaque type Ply = Int
|
||||
object Ply extends RelaxedOpaqueInt[Ply]:
|
||||
val initial: Ply = 0
|
||||
val initial: Ply = 0
|
||||
val firstMove: Ply = 1
|
||||
extension (e: Ply)
|
||||
inline def turn: Color = Color.fromWhite(e.isEven) // whose turn it is to play now
|
||||
inline def turn: Color = Color.fromWhite(e.isEven) // whose turn it is to play now
|
||||
def fullMoveNumber: FullMoveNumber = FullMoveNumber(1 + e / 2)
|
||||
inline def isEven: Boolean = (e & 1) == 0
|
||||
inline def isOdd: Boolean = !e.isEven
|
||||
inline def next: Ply = Ply(e + 1)
|
||||
inline def isEven: Boolean = (e & 1) == 0
|
||||
inline def isOdd: Boolean = !e.isEven
|
||||
inline def next: Ply = Ply(e + 1)
|
||||
|
||||
/* The halfmove clock specifies a decimal number of half moves with respect to the 50 move draw rule.
|
||||
* It is reset to zero after a capture or a pawn move and incremented otherwise. */
|
||||
|
||||
@@ -14,12 +14,12 @@ final class Opening(
|
||||
|
||||
val (family: OpeningFamily, variation: Option[OpeningVariation]) = name.value.split(":", 2) match
|
||||
case Array(f, v) => OpeningFamily(OpeningName(f)) -> Some(OpeningVariation(v.takeWhile(',' !=).trim))
|
||||
case Array(f) => OpeningFamily(OpeningName(f)) -> None
|
||||
case _ => OpeningFamily(name) -> None
|
||||
case Array(f) => OpeningFamily(OpeningName(f)) -> None
|
||||
case _ => OpeningFamily(name) -> None
|
||||
|
||||
lazy val nbMoves: Int = uci.value.count(' ' ==) + 1
|
||||
lazy val nbMoves: Int = uci.value.count(' ' ==) + 1
|
||||
lazy val lastUci: Option[Uci.Move] = uci.value.split(' ').lastOption.flatMap(Uci.Move.apply)
|
||||
lazy val key: OpeningKey = Opening.nameToKey(name)
|
||||
lazy val key: OpeningKey = Opening.nameToKey(name)
|
||||
|
||||
override def toString = name.value
|
||||
|
||||
@@ -34,8 +34,8 @@ object Opening:
|
||||
|
||||
object nameToKey:
|
||||
private val splitAccentRegex = "[\u0300-\u036f]".r
|
||||
private val multiSpaceRegex = """\s+""".r
|
||||
private val badChars = """[^\w\-]+""".r
|
||||
private val multiSpaceRegex = """\s+""".r
|
||||
private val badChars = """[^\w\-]+""".r
|
||||
def apply(name: OpeningName) = OpeningKey:
|
||||
badChars.replaceAllIn(
|
||||
multiSpaceRegex.replaceAllIn(
|
||||
|
||||
@@ -20,7 +20,7 @@ object OpeningDb:
|
||||
.foldLeft(Map.empty) { case (acc, op) =>
|
||||
acc.updatedWith(op.key):
|
||||
case Some(prev) if prev.uci.value.size < op.uci.value.size => prev.some
|
||||
case _ => op.some
|
||||
case _ => op.some
|
||||
}
|
||||
|
||||
def isShortest(op: Opening) = shortestLines.get(op.key).contains(op)
|
||||
@@ -29,7 +29,7 @@ object OpeningDb:
|
||||
|
||||
def findByStandardFen(fen: StandardFen): Option[Opening] = byFen.get(fen)
|
||||
|
||||
val SEARCH_MAX_PLIES = 40
|
||||
val SEARCH_MAX_PLIES = 40
|
||||
val SEARCH_MIN_PIECES = 20
|
||||
|
||||
// assumes standard initial Fen and variant
|
||||
@@ -46,7 +46,7 @@ object OpeningDb:
|
||||
.take(SEARCH_MAX_PLIES)
|
||||
.takeWhile:
|
||||
case move: Move => move.before.board.nbPieces >= SEARCH_MIN_PIECES
|
||||
case _ => false
|
||||
case _ => false
|
||||
.collect { case move: Move => move }
|
||||
.toVector
|
||||
moves.map(_.before) ++ moves.lastOption.map(_.after).toVector
|
||||
@@ -59,9 +59,9 @@ object OpeningDb:
|
||||
.drop(1)
|
||||
.foldRight(none[Opening.AtPly]):
|
||||
case ((board, ply), None) => byFen.get(format.Fen.writeOpening(board)).map(_.atPly(Ply(ply)))
|
||||
case (_, found) => found
|
||||
case (_, found) => found
|
||||
|
||||
def searchInFens(fens: Iterable[StandardFen]): Option[Opening] =
|
||||
fens.foldRight(none[Opening]):
|
||||
case (fen, None) => findByStandardFen(fen)
|
||||
case (_, found) => found
|
||||
case (_, found) => found
|
||||
|
||||
@@ -14,11 +14,11 @@ case object Antichess
|
||||
standardInitialPosition = true
|
||||
):
|
||||
|
||||
override val initialBoard: Board = Board.standard
|
||||
override val initialBoard: Board = Board.standard
|
||||
override def initialPieces: Map[Square, Piece] = initialBoard.pieceMap
|
||||
|
||||
// In antichess, it is not permitted to castle
|
||||
override val castles: Castles = Castles.none
|
||||
override val castles: Castles = Castles.none
|
||||
override val initialFen: FullFen = FullFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1")
|
||||
|
||||
// In antichess, the king can't be put into check so we always return false
|
||||
|
||||
@@ -12,7 +12,7 @@ case object Atomic
|
||||
standardInitialPosition = true
|
||||
):
|
||||
|
||||
override val initialBoard: Board = Board.standard
|
||||
override val initialBoard: Board = Board.standard
|
||||
override def initialPieces: Map[Square, Piece] = initialBoard.pieceMap
|
||||
|
||||
override def validMoves(position: Position): List[Move] =
|
||||
@@ -31,7 +31,7 @@ case object Atomic
|
||||
position.kingsOnlyOf(!position.color)
|
||||
|
||||
/** Atomic chess has a special end where a king has been killed by exploding with an adjacent captured piece */
|
||||
override def specialEnd(position: Position): Boolean = position.kings.count < 2
|
||||
override def specialEnd(position: Position): Boolean = position.kings.count < 2
|
||||
override def validMovesAt(position: Position, square: Square): List[Move] =
|
||||
super.validMovesAt(position, square).view.map(explodeSurroundingPieces).filter(kingSafety).toList
|
||||
|
||||
@@ -84,13 +84,13 @@ case object Atomic
|
||||
// Pawns are immune (for some reason), but all pieces surrounding the captured piece and the capturing piece
|
||||
// itself explode
|
||||
val squaresToExplode = (move.dest.kingAttacks & afterBoard.occupied & ~afterBoard.pawns) | move.dest.bl
|
||||
val afterExplosions = afterBoard.withBoard(afterBoard.board.discard(squaresToExplode))
|
||||
val afterExplosions = afterBoard.withBoard(afterBoard.board.discard(squaresToExplode))
|
||||
|
||||
val rooksToExploded = squaresToExplode & afterBoard.rooks
|
||||
|
||||
val castles = afterBoard.castles & ~rooksToExploded
|
||||
val castles = afterBoard.castles & ~rooksToExploded
|
||||
val unMovedRooks = afterBoard.unmovedRooks & ~rooksToExploded
|
||||
val newBoard = afterExplosions.updateHistory(_.copy(castles = castles, unmovedRooks = unMovedRooks))
|
||||
val newBoard = afterExplosions.updateHistory(_.copy(castles = castles, unmovedRooks = unMovedRooks))
|
||||
move.copy(afterWithoutHistory = newBoard)
|
||||
else move
|
||||
|
||||
@@ -124,10 +124,10 @@ case object Atomic
|
||||
InsufficientMatingMaterial.pawnBlockedByPawn(square, position)
|
||||
|| piece.is(King) || piece.is(Bishop)
|
||||
)
|
||||
val randomBishop = position.pieces.find { case (_, piece) => piece.is(Bishop) }
|
||||
val randomBishop = position.pieces.find { case (_, piece) => piece.is(Bishop) }
|
||||
val bishopsAbsentOrPawnitized = randomBishop match
|
||||
case Some((square, piece)) => bishopPawnitized(position.board, piece.color, square.isLight)
|
||||
case None => true
|
||||
case None => true
|
||||
closedStructure && bishopsAbsentOrPawnitized
|
||||
|
||||
private def bishopPawnitized(board: Board, sideWithBishop: Color, bishopLight: Boolean) =
|
||||
|
||||
@@ -15,7 +15,7 @@ case object Crazyhouse
|
||||
standardInitialPosition = true
|
||||
):
|
||||
|
||||
override val initialBoard: Board = Board.standard
|
||||
override val initialBoard: Board = Board.standard
|
||||
override def initialPieces: Map[Square, Piece] = initialBoard.pieceMap
|
||||
|
||||
override val initialFen: FullFen = FullFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR/ w KQkq - 0 1")
|
||||
@@ -36,7 +36,7 @@ case object Crazyhouse
|
||||
override def drop(position: Position, role: Role, square: Square): Either[ErrorStr, Drop] =
|
||||
for
|
||||
d1 <- position.crazyData.toRight(ErrorStr("Position has no crazyhouse data"))
|
||||
_ <- Either.cond((role != Pawn || canDropPawnOn(square)), d1, ErrorStr(s"Can't drop $role on $square"))
|
||||
_ <- Either.cond((role != Pawn || canDropPawnOn(square)), d1, ErrorStr(s"Can't drop $role on $square"))
|
||||
piece = Piece(position.color, role)
|
||||
d2 <- d1.drop(piece).toRight(ErrorStr(s"No $piece to drop on $square"))
|
||||
b1 <- position.board
|
||||
@@ -62,7 +62,7 @@ case object Crazyhouse
|
||||
|
||||
// there is always sufficient mating material in Crazyhouse
|
||||
override def opponentHasInsufficientMaterial(position: Position): Boolean = false
|
||||
override def isInsufficientMaterial(position: Position): Boolean = false
|
||||
override def isInsufficientMaterial(position: Position): Boolean = false
|
||||
|
||||
// if the king is not in check, all drops are possible, we just return None
|
||||
// king is in single check, we return the squares between the king and the checker
|
||||
@@ -128,7 +128,7 @@ case object Crazyhouse
|
||||
to <- if role == Pawn then targets & ~Bitboard.firstRank & ~Bitboard.lastRank else targets
|
||||
piece = Piece(position.color, role)
|
||||
after <- position.board.put(piece, to)
|
||||
d2 <- data.drop(piece)
|
||||
d2 <- data.drop(piece)
|
||||
yield Drop(piece, to, position, position.withBoard(after).withCrazyData(d2))
|
||||
|
||||
type Pockets = ByColor[Pocket]
|
||||
@@ -157,7 +157,7 @@ case object Crazyhouse
|
||||
if promoted.contains(orig) then copy(promoted = promoted.move(orig, dest)) else this
|
||||
|
||||
def isEmpty = pockets.forall(_.isEmpty)
|
||||
def size = pockets.reduce(_.size + _.size)
|
||||
def size = pockets.reduce(_.size + _.size)
|
||||
|
||||
object Data:
|
||||
val init: Data = Data(Pockets.empty, Bitboard.empty)
|
||||
@@ -175,32 +175,32 @@ case object Crazyhouse
|
||||
case class Pocket(pawn: Int, knight: Int, bishop: Int, rook: Int, queen: Int):
|
||||
|
||||
def forsythUpper: String = forsyth.toUpperCase
|
||||
def forsyth: String = forsyth(pawn, 'p') + forsyth(knight, 'n') +
|
||||
def forsyth: String = forsyth(pawn, 'p') + forsyth(knight, 'n') +
|
||||
forsyth(bishop, 'b') + forsyth(rook, 'r') + forsyth(queen, 'q')
|
||||
|
||||
def forsyth(role: Int, char: Char): String = List.fill(role)(char).mkString
|
||||
|
||||
def size: Int = pawn + knight + bishop + rook + queen
|
||||
def isEmpty: Boolean = size == 0
|
||||
def nonEmpty: Boolean = size > 0
|
||||
def size: Int = pawn + knight + bishop + rook + queen
|
||||
def isEmpty: Boolean = size == 0
|
||||
def nonEmpty: Boolean = size > 0
|
||||
def hasNonPawn: Boolean = knight + bishop + rook + queen > 0
|
||||
|
||||
def contains(r: Role): Boolean = r match
|
||||
case Pawn => pawn > 0
|
||||
case Pawn => pawn > 0
|
||||
case Knight => knight > 0
|
||||
case Bishop => bishop > 0
|
||||
case Rook => rook > 0
|
||||
case Queen => queen > 0
|
||||
case King => false
|
||||
case Rook => rook > 0
|
||||
case Queen => queen > 0
|
||||
case King => false
|
||||
|
||||
def apply(role: Role): Option[Int] =
|
||||
role match
|
||||
case Pawn => Some(pawn)
|
||||
case Pawn => Some(pawn)
|
||||
case Knight => Some(knight)
|
||||
case Bishop => Some(bishop)
|
||||
case Rook => Some(rook)
|
||||
case Queen => Some(queen)
|
||||
case King => None
|
||||
case Rook => Some(rook)
|
||||
case Queen => Some(queen)
|
||||
case King => None
|
||||
|
||||
def take(role: Role): Option[Pocket] =
|
||||
update(role, (x => Option.when(x > 0)(x - 1)))
|
||||
@@ -208,20 +208,20 @@ case object Crazyhouse
|
||||
def store(role: Role): Pocket = update(role, _ + 1)
|
||||
|
||||
def update(role: Role, f: Int => Int): Pocket = role match
|
||||
case Pawn => copy(pawn = f(pawn))
|
||||
case Pawn => copy(pawn = f(pawn))
|
||||
case Knight => copy(knight = f(knight))
|
||||
case Bishop => copy(bishop = f(bishop))
|
||||
case Rook => copy(rook = f(rook))
|
||||
case Queen => copy(queen = f(queen))
|
||||
case King => this
|
||||
case Rook => copy(rook = f(rook))
|
||||
case Queen => copy(queen = f(queen))
|
||||
case King => this
|
||||
|
||||
def update(role: Role, f: Int => Option[Int]): Option[Pocket] = role match
|
||||
case Pawn => f(pawn).map(x => copy(pawn = x))
|
||||
case Pawn => f(pawn).map(x => copy(pawn = x))
|
||||
case Knight => f(knight).map(x => copy(knight = x))
|
||||
case Bishop => f(bishop).map(x => copy(bishop = x))
|
||||
case Rook => f(rook).map(x => copy(rook = x))
|
||||
case Queen => f(queen).map(x => copy(queen = x))
|
||||
case King => None
|
||||
case Rook => f(rook).map(x => copy(rook = x))
|
||||
case Queen => f(queen).map(x => copy(queen = x))
|
||||
case King => None
|
||||
|
||||
def flatMap[B](f: (Role, Int) => IterableOnce[B]): List[B] =
|
||||
List(f(Pawn, pawn), f(Knight, knight), f(Bishop, bishop), f(Rook, rook), f(Queen, queen)).flatten
|
||||
@@ -240,44 +240,44 @@ case object Crazyhouse
|
||||
val empty: Pocket = Pocket(0, 0, 0, 0, 0)
|
||||
|
||||
def apply(roles: Seq[Role]): Pocket =
|
||||
var pawn = 0
|
||||
var pawn = 0
|
||||
var knight = 0
|
||||
var bishop = 0
|
||||
var rook = 0
|
||||
var queen = 0
|
||||
var rook = 0
|
||||
var queen = 0
|
||||
roles.foreach:
|
||||
case Pawn => pawn += 1
|
||||
case Pawn => pawn += 1
|
||||
case Knight => knight += 1
|
||||
case Bishop => bishop += 1
|
||||
case Rook => rook += 1
|
||||
case Queen => queen += 1
|
||||
case King =>
|
||||
case Rook => rook += 1
|
||||
case Queen => queen += 1
|
||||
case King =>
|
||||
Pocket(pawn, knight, bishop, rook, queen)
|
||||
|
||||
object Pockets:
|
||||
inline def apply(pieces: Seq[Piece]): Pockets =
|
||||
var whitePawn = 0
|
||||
var whitePawn = 0
|
||||
var whiteKnight = 0
|
||||
var whiteBishop = 0
|
||||
var whiteRook = 0
|
||||
var whiteQueen = 0
|
||||
var blackPawn = 0
|
||||
var whiteRook = 0
|
||||
var whiteQueen = 0
|
||||
var blackPawn = 0
|
||||
var blackKnight = 0
|
||||
var blackBishop = 0
|
||||
var blackRook = 0
|
||||
var blackQueen = 0
|
||||
var blackRook = 0
|
||||
var blackQueen = 0
|
||||
pieces.foreach:
|
||||
case Piece(White, Pawn) => whitePawn += 1
|
||||
case Piece(White, Pawn) => whitePawn += 1
|
||||
case Piece(White, Knight) => whiteKnight += 1
|
||||
case Piece(White, Bishop) => whiteBishop += 1
|
||||
case Piece(White, Rook) => whiteRook += 1
|
||||
case Piece(White, Queen) => whiteQueen += 1
|
||||
case Piece(Black, Pawn) => blackPawn += 1
|
||||
case Piece(White, Rook) => whiteRook += 1
|
||||
case Piece(White, Queen) => whiteQueen += 1
|
||||
case Piece(Black, Pawn) => blackPawn += 1
|
||||
case Piece(Black, Knight) => blackKnight += 1
|
||||
case Piece(Black, Bishop) => blackBishop += 1
|
||||
case Piece(Black, Rook) => blackRook += 1
|
||||
case Piece(Black, Queen) => blackQueen += 1
|
||||
case Piece(_, King) =>
|
||||
case Piece(Black, Rook) => blackRook += 1
|
||||
case Piece(Black, Queen) => blackQueen += 1
|
||||
case Piece(_, King) =>
|
||||
ByColor(
|
||||
Pocket(whitePawn, whiteKnight, whiteBishop, whiteRook, whiteQueen),
|
||||
Pocket(blackPawn, blackKnight, blackBishop, blackRook, blackQueen)
|
||||
|
||||
@@ -12,7 +12,7 @@ case object FromPosition
|
||||
standardInitialPosition = false
|
||||
):
|
||||
|
||||
override val initialBoard: Board = Board.standard
|
||||
override val initialBoard: Board = Board.standard
|
||||
override def initialPieces: Map[Square, Piece] = initialBoard.pieceMap
|
||||
|
||||
override def validMoves(position: Position): List[Move] =
|
||||
|
||||
@@ -23,8 +23,8 @@ case object Horde
|
||||
x <- File.all
|
||||
y <- Rank.all.take(4)
|
||||
yield (Square(x, y) -> White.pawn)
|
||||
val frontPawns = List(Square.B5, Square.C5, Square.F5, Square.G5).map { _ -> White.pawn }
|
||||
val blackPawns = File.all.map { Square(_, Rank.Seventh) -> Black.pawn }
|
||||
val frontPawns = List(Square.B5, Square.C5, Square.F5, Square.G5).map { _ -> White.pawn }
|
||||
val blackPawns = File.all.map { Square(_, Rank.Seventh) -> Black.pawn }
|
||||
val blackPieces = File.all.map { x => Square(x, Rank.Eighth) -> (Black - backRank(x.value)) }
|
||||
(whitePawnsHorde ++ frontPawns ++ blackPawns ++ blackPieces).toMap
|
||||
|
||||
@@ -76,7 +76,7 @@ case object Horde
|
||||
*/
|
||||
private def hordeClosedPosition(position: Position): Boolean =
|
||||
val hordeSquare = position.byColor(White)
|
||||
val mateInOne = hordeSquare.count == 1 &&
|
||||
val mateInOne = hordeSquare.count == 1 &&
|
||||
hordeSquare.singleSquare.exists(pieceThreatened(position.board, Color.black, _))
|
||||
!mateInOne && {
|
||||
if position.isWhiteTurn then position.legalMoves.isEmpty
|
||||
@@ -96,20 +96,20 @@ case object Horde
|
||||
// Black can always win by capturing the horde
|
||||
if color.black then false
|
||||
else
|
||||
val hordeRole = board.byRoleOf(color)
|
||||
val horde = hordeRole.map(_.count)
|
||||
val hordeRole = board.byRoleOf(color)
|
||||
val horde = hordeRole.map(_.count)
|
||||
val hordeBishops: SquareColor => Int = color => (hordeRole.bishop & color.bb).count
|
||||
val hordeBishopColor = if hordeBishops(Light) >= 1 then Light else Dark
|
||||
val hordeBishopColor = if hordeBishops(Light) >= 1 then Light else Dark
|
||||
|
||||
val hordeBishopNum = Math.min(hordeBishops(Light), 2) + Math.min(hordeBishops(Dark), 2)
|
||||
// Two same color bishops suffice to cover all the light and dark squares
|
||||
// around the enemy king.
|
||||
val hordeNum = horde.pawn + horde.knight + horde.rook + horde.queen + hordeBishopNum
|
||||
val hordeNum = horde.pawn + horde.knight + horde.rook + horde.queen + hordeBishopNum
|
||||
val piecesRole = board.byRoleOf(Color.black)
|
||||
val pieces = piecesRole.map(_.count)
|
||||
val pieces = piecesRole.map(_.count)
|
||||
val piecesBishops: SquareColor => Int = color => (piecesRole.bishop & color.bb).count
|
||||
val piecesNum = piecesRole.map(_.count).values.sum
|
||||
val piecesOfTypeNot = (pieces: Int) => piecesNum - pieces
|
||||
val piecesNum = piecesRole.map(_.count).values.sum
|
||||
val piecesOfTypeNot = (pieces: Int) => piecesNum - pieces
|
||||
if hordeNum == 0 then true
|
||||
else if hordeNum >= 4 then false // Four or more white pieces can always deliver mate.
|
||||
// Pawns/queens are never insufficient material when paired with any other
|
||||
@@ -142,7 +142,7 @@ case object Horde
|
||||
else if horde.pawn == 1 then
|
||||
// Promote the pawn to a queen or a knight and check whether white
|
||||
// can mate.
|
||||
val pawnSquare = (board.pawns & board.byColor(Color.white)).first.get // we know there is a pawn
|
||||
val pawnSquare = (board.pawns & board.byColor(Color.white)).first.get // we know there is a pawn
|
||||
val promoteToQueen = board.putOrReplace(White.queen, pawnSquare)
|
||||
val promoteToKnight = board.putOrReplace(White.knight, pawnSquare)
|
||||
hasInsufficientMaterial(promoteToQueen, color) && hasInsufficientMaterial(promoteToKnight, color)
|
||||
@@ -249,8 +249,8 @@ enum SquareColor:
|
||||
|
||||
def bb: Bitboard = this match
|
||||
case Light => Bitboard.lightSquares
|
||||
case Dark => Bitboard.darkSquares
|
||||
case Dark => Bitboard.darkSquares
|
||||
|
||||
def unary_! : SquareColor = this match
|
||||
case Light => Dark
|
||||
case Dark => Light
|
||||
case Dark => Light
|
||||
|
||||
@@ -12,7 +12,7 @@ case object KingOfTheHill
|
||||
standardInitialPosition = true
|
||||
):
|
||||
|
||||
override val initialBoard: Board = Board.standard
|
||||
override val initialBoard: Board = Board.standard
|
||||
override def initialPieces: Map[Square, Piece] = initialBoard.pieceMap
|
||||
|
||||
override def validMoves(position: Position): List[Move] =
|
||||
@@ -30,4 +30,4 @@ case object KingOfTheHill
|
||||
/** You only need a king to be able to win in this variant
|
||||
*/
|
||||
override def opponentHasInsufficientMaterial(position: Position): Boolean = false
|
||||
override def isInsufficientMaterial(position: Position): Boolean = false
|
||||
override def isInsufficientMaterial(position: Position): Boolean = false
|
||||
|
||||
@@ -37,7 +37,7 @@ case object RacingKings
|
||||
)
|
||||
override val initialBoard: Board = Board.fromMap(initialPieces)
|
||||
|
||||
override val castles: Castles = Castles.none
|
||||
override val castles: Castles = Castles.none
|
||||
override val allowsCastling: Boolean = false
|
||||
|
||||
override val initialFen: FullFen = FullFen("8/8/8/8/8/8/krbnNBRK/qrbnNBRQ w - - 0 1")
|
||||
@@ -53,7 +53,7 @@ case object RacingKings
|
||||
override def valid(position: Position, strict: Boolean): Boolean =
|
||||
super.valid(position, strict) && (!strict || position.check.no)
|
||||
|
||||
override def isInsufficientMaterial(position: Position): Boolean = false
|
||||
override def isInsufficientMaterial(position: Position): Boolean = false
|
||||
override def opponentHasInsufficientMaterial(position: Position): Boolean = false
|
||||
|
||||
// It is a win, when exactly one king made it to the goal. When white reaches
|
||||
|
||||
@@ -12,9 +12,9 @@ case object Standard
|
||||
standardInitialPosition = true
|
||||
):
|
||||
|
||||
override val initialBoard: Board = Board.standard
|
||||
override val initialBoard: Board = Board.standard
|
||||
override def initialPieces: Map[Square, Piece] = initialBoard.pieceMap
|
||||
override val initialPosition: Position = Position(initialBoard, this, White)
|
||||
override val initialPosition: Position = Position(initialBoard, this, White)
|
||||
|
||||
override def valid(position: Position, strict: Boolean): Boolean =
|
||||
super.valid(position, strict) && (!strict || hasValidCheckers(position))
|
||||
@@ -23,7 +23,7 @@ case object Standard
|
||||
import position.{ genNonKing, genSafeKing, genCastling, color, ourKing }
|
||||
val enPassantMoves = position.genEnPassant(position.us & position.pawns)
|
||||
ourKing.fold(Nil): king =>
|
||||
val checkers = position.attackers(king, !position.color)
|
||||
val checkers = position.attackers(king, !position.color)
|
||||
val candidates =
|
||||
if checkers.isEmpty then
|
||||
val targets = ~position.us
|
||||
@@ -51,10 +51,10 @@ case object Standard
|
||||
private def genEvasions(king: Square, position: Position, checkers: Bitboard): List[Move] =
|
||||
import position.{ genNonKing, genSafeKing, us }
|
||||
// Checks by these sliding pieces can maybe be blocked.
|
||||
val sliders = checkers & position.sliders
|
||||
val attacked = sliders.fold(Bitboard.empty)((a, s) => a | (Bitboard.ray(king, s) ^ s.bl))
|
||||
val sliders = checkers & position.sliders
|
||||
val attacked = sliders.fold(Bitboard.empty)((a, s) => a | (Bitboard.ray(king, s) ^ s.bl))
|
||||
val safeKings = genSafeKing(king, ~us & ~attacked)
|
||||
val blockers = checkers.singleSquare.fold(Nil)(c => genNonKing(Bitboard.between(king, c) | checkers))
|
||||
val blockers = checkers.singleSquare.fold(Nil)(c => genNonKing(Bitboard.between(king, c) | checkers))
|
||||
safeKings ++ blockers
|
||||
|
||||
def hasValidCheckers(position: Position): Boolean =
|
||||
@@ -66,8 +66,8 @@ case object Standard
|
||||
private def isValidCheckersForEnPassant(position: Position, activeCheckers: Bitboard): Boolean =
|
||||
(for
|
||||
enPassantSquare <- position.potentialEpSquare
|
||||
enPassantUp <- enPassantSquare.prevRank(position.color)
|
||||
enPassantDown <- enPassantSquare.nextRank(position.color)
|
||||
enPassantUp <- enPassantSquare.prevRank(position.color)
|
||||
enPassantDown <- enPassantSquare.nextRank(position.color)
|
||||
yield activeCheckers.count == 1 && (
|
||||
activeCheckers.first.contains(enPassantSquare) || position.board
|
||||
.move(enPassantUp, enPassantDown)
|
||||
@@ -81,7 +81,7 @@ case object Standard
|
||||
else
|
||||
(for
|
||||
firstChecker <- activeCheckers.first
|
||||
lastChecker <- activeCheckers.last
|
||||
ourKing <- position.ourKing
|
||||
lastChecker <- activeCheckers.last
|
||||
ourKing <- position.ourKing
|
||||
yield !Bitboard.aligned(firstChecker, lastChecker, ourKing))
|
||||
.getOrElse(false)
|
||||
|
||||
@@ -14,7 +14,7 @@ case object ThreeCheck
|
||||
standardInitialPosition = true
|
||||
):
|
||||
|
||||
override val initialBoard: Board = Board.standard
|
||||
override val initialBoard: Board = Board.standard
|
||||
override def initialPieces: Map[Square, Piece] = initialBoard.pieceMap
|
||||
|
||||
override val initialFen: FullFen = FullFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 +0+0")
|
||||
|
||||
@@ -22,16 +22,16 @@ abstract class Variant private[variant] (
|
||||
def initialBoard: Board
|
||||
def initialPosition: Position = Position(initialBoard, this, White)
|
||||
|
||||
inline def standard: Boolean = this == Standard
|
||||
inline def chess960: Boolean = this == Chess960
|
||||
inline def fromPosition: Boolean = this == FromPosition
|
||||
inline def standard: Boolean = this == Standard
|
||||
inline def chess960: Boolean = this == Chess960
|
||||
inline def fromPosition: Boolean = this == FromPosition
|
||||
inline def kingOfTheHill: Boolean = this == KingOfTheHill
|
||||
inline def threeCheck: Boolean = this == ThreeCheck
|
||||
inline def antichess: Boolean = this == Antichess
|
||||
inline def atomic: Boolean = this == Atomic
|
||||
inline def horde: Boolean = this == Horde
|
||||
inline def racingKings: Boolean = this == RacingKings
|
||||
inline def crazyhouse: Boolean = this == Crazyhouse
|
||||
inline def threeCheck: Boolean = this == ThreeCheck
|
||||
inline def antichess: Boolean = this == Antichess
|
||||
inline def atomic: Boolean = this == Atomic
|
||||
inline def horde: Boolean = this == Horde
|
||||
inline def racingKings: Boolean = this == RacingKings
|
||||
inline def crazyhouse: Boolean = this == Crazyhouse
|
||||
|
||||
inline def exotic: Boolean = !standard
|
||||
|
||||
@@ -49,9 +49,9 @@ abstract class Variant private[variant] (
|
||||
|
||||
def isValidPromotion(promotion: Option[PromotableRole]): Boolean =
|
||||
promotion match
|
||||
case None => true
|
||||
case None => true
|
||||
case Some(Queen | Rook | Knight | Bishop) => true
|
||||
case _ => false
|
||||
case _ => false
|
||||
|
||||
def validMoves(position: Position): List[Move]
|
||||
|
||||
@@ -61,14 +61,14 @@ abstract class Variant private[variant] (
|
||||
if piece.color != position.color then Nil
|
||||
else
|
||||
val targets = ~us
|
||||
val bb = square.bb
|
||||
val bb = square.bb
|
||||
piece.role match
|
||||
case Pawn => position.genEnPassant(us & bb) ++ position.genPawn(bb, targets)
|
||||
case Pawn => position.genEnPassant(us & bb) ++ position.genPawn(bb, targets)
|
||||
case Knight => position.genKnight(us & bb, targets)
|
||||
case Bishop => position.genBishop(us & bb, targets)
|
||||
case Rook => position.genRook(us & bb, targets)
|
||||
case Queen => position.genQueen(us & bb, targets)
|
||||
case King => position.genKingAt(targets, square)
|
||||
case Rook => position.genRook(us & bb, targets)
|
||||
case Queen => position.genQueen(us & bb, targets)
|
||||
case King => position.genKingAt(targets, square)
|
||||
}
|
||||
|
||||
def pieceThreatened(board: Board, by: Color, to: Square): Boolean =
|
||||
@@ -212,7 +212,7 @@ object Variant:
|
||||
Horde,
|
||||
RacingKings
|
||||
)
|
||||
val byId = all.mapBy(_.id)
|
||||
val byId = all.mapBy(_.id)
|
||||
val byKey = all.mapBy(_.key)
|
||||
|
||||
val openingSensibleVariants: Set[Variant] = Set(
|
||||
@@ -231,12 +231,12 @@ object Variant:
|
||||
|
||||
inline def default: Variant = Standard
|
||||
|
||||
inline def apply(inline id: Id): Option[Variant] = list.byId.get(id)
|
||||
inline def apply(inline id: Id): Option[Variant] = list.byId.get(id)
|
||||
inline def apply(inline key: LilaKey): Option[Variant] = list.byKey.get(key)
|
||||
def orDefault(id: Id): Variant = apply(id) | default
|
||||
def orDefault(key: LilaKey): Variant = apply(key) | default
|
||||
def idOrDefault(id: Option[Id]): Variant = id.flatMap(apply(_)) | default
|
||||
def orDefault(key: Option[LilaKey]): Variant = key.flatMap(apply(_)) | default
|
||||
def orDefault(id: Id): Variant = apply(id) | default
|
||||
def orDefault(key: LilaKey): Variant = apply(key) | default
|
||||
def idOrDefault(id: Option[Id]): Variant = id.flatMap(apply(_)) | default
|
||||
def orDefault(key: Option[LilaKey]): Variant = key.flatMap(apply(_)) | default
|
||||
|
||||
def byName(name: String): Option[Variant] =
|
||||
list.all.find(_.name.toLowerCase == name.toLowerCase)
|
||||
|
||||
@@ -13,7 +13,7 @@ object Json:
|
||||
|
||||
given Writes[chess.Color] = LibJson.writeAs(_.name)
|
||||
|
||||
given Reads[Uci] = LibJson.optRead(Uci.apply)
|
||||
given Reads[Uci] = LibJson.optRead(Uci.apply)
|
||||
given Writes[Uci] = LibJson.writeAs(_.uci)
|
||||
|
||||
given OWrites[Crazyhouse.Pocket] = OWrites: p =>
|
||||
@@ -32,7 +32,7 @@ object Json:
|
||||
given Writes[Opening] with
|
||||
def writes(o: Opening) = PlayJson.obj("eco" -> o.eco, "name" -> o.name)
|
||||
|
||||
given Writes[Glyph] = PlayJson.writes[Glyph]
|
||||
given Writes[Glyph] = PlayJson.writes[Glyph]
|
||||
given Writes[Glyphs] = Writes[Glyphs]: gs =>
|
||||
PlayJson.toJson(gs.toList)
|
||||
|
||||
@@ -49,20 +49,20 @@ object Json:
|
||||
given OWrites[CorrespondenceClock] = OWrites: c =>
|
||||
PlayJson.obj(
|
||||
"daysPerTurn" -> c.daysPerTurn,
|
||||
"increment" -> c.increment,
|
||||
"white" -> c.whiteTime,
|
||||
"black" -> c.blackTime
|
||||
"increment" -> c.increment,
|
||||
"white" -> c.whiteTime,
|
||||
"black" -> c.blackTime
|
||||
)
|
||||
|
||||
given OWrites[chess.opening.Opening.AtPly] = OWrites: o =>
|
||||
PlayJson.obj(
|
||||
"eco" -> o.opening.eco,
|
||||
"eco" -> o.opening.eco,
|
||||
"name" -> o.opening.name,
|
||||
"ply" -> o.ply
|
||||
"ply" -> o.ply
|
||||
)
|
||||
|
||||
def destString(dests: Map[Square, Bitboard]): String =
|
||||
val sb = new java.lang.StringBuilder(80)
|
||||
val sb = new java.lang.StringBuilder(80)
|
||||
var first = true
|
||||
dests.foreach: (orig, dests) =>
|
||||
if first then first = false
|
||||
|
||||
+116
-116
@@ -24,7 +24,7 @@ object Elo extends RichOpaqueInt[Elo]:
|
||||
val prd = playersRatingDiff(player.rating, game.opponentRating)
|
||||
getExpectedScore(prd)
|
||||
val achievedScore = games.foldMap(_.points.value)
|
||||
val ratingDiff =
|
||||
val ratingDiff =
|
||||
Math.round(player.kFactor * (achievedScore - expectedScore))
|
||||
player.rating + ratingDiff
|
||||
|
||||
@@ -42,7 +42,7 @@ object Elo extends RichOpaqueInt[Elo]:
|
||||
def computePerformanceRating(games: Seq[Game]): Option[Elo] =
|
||||
games.nonEmpty.option:
|
||||
val averageOpponentRating = games.map(_.opponentRating).sum / games.size
|
||||
val percentageScore = Math.round(games.map(_.points.value).sum * 100 / games.size)
|
||||
val percentageScore = Math.round(games.map(_.points.value).sum * 100 / games.size)
|
||||
averageOpponentRating + performanceRatingTableFIDE.getOrElse(percentageScore, 0)
|
||||
|
||||
final class Player(val rating: Elo, val kFactor: KFactor)
|
||||
@@ -51,20 +51,20 @@ object Elo extends RichOpaqueInt[Elo]:
|
||||
// 8.1.2 FIDE table
|
||||
// We use the full table for perfect tournament performance rating (PTP) calculations (no +- 400 limit)
|
||||
val conversionTableFIDE: Map[Int, Float] = List(
|
||||
3 -> 0.50f,
|
||||
10 -> 0.51f,
|
||||
17 -> 0.52f,
|
||||
25 -> 0.53f,
|
||||
32 -> 0.54f,
|
||||
39 -> 0.55f,
|
||||
46 -> 0.56f,
|
||||
53 -> 0.57f,
|
||||
61 -> 0.58f,
|
||||
68 -> 0.59f,
|
||||
76 -> 0.60f,
|
||||
83 -> 0.61f,
|
||||
91 -> 0.62f,
|
||||
98 -> 0.63f,
|
||||
3 -> 0.50f,
|
||||
10 -> 0.51f,
|
||||
17 -> 0.52f,
|
||||
25 -> 0.53f,
|
||||
32 -> 0.54f,
|
||||
39 -> 0.55f,
|
||||
46 -> 0.56f,
|
||||
53 -> 0.57f,
|
||||
61 -> 0.58f,
|
||||
68 -> 0.59f,
|
||||
76 -> 0.60f,
|
||||
83 -> 0.61f,
|
||||
91 -> 0.62f,
|
||||
98 -> 0.63f,
|
||||
106 -> 0.64f,
|
||||
113 -> 0.65f,
|
||||
121 -> 0.66f,
|
||||
@@ -113,105 +113,105 @@ object Elo extends RichOpaqueInt[Elo]:
|
||||
|
||||
// 1.4.9 FIDE table
|
||||
val performanceRatingTableFIDE: Map[Int, Int] = Map(
|
||||
0 -> -800,
|
||||
1 -> -677,
|
||||
2 -> -589,
|
||||
3 -> -538,
|
||||
4 -> -501,
|
||||
5 -> -470,
|
||||
6 -> -444,
|
||||
7 -> -422,
|
||||
8 -> -401,
|
||||
9 -> -383,
|
||||
10 -> -366,
|
||||
11 -> -351,
|
||||
12 -> -336,
|
||||
13 -> -322,
|
||||
14 -> -309,
|
||||
15 -> -296,
|
||||
16 -> -284,
|
||||
17 -> -273,
|
||||
18 -> -262,
|
||||
19 -> -251,
|
||||
20 -> -240,
|
||||
21 -> -230,
|
||||
22 -> -220,
|
||||
23 -> -211,
|
||||
24 -> -202,
|
||||
25 -> -193,
|
||||
26 -> -184,
|
||||
27 -> -175,
|
||||
28 -> -166,
|
||||
29 -> -158,
|
||||
30 -> -149,
|
||||
31 -> -141,
|
||||
32 -> -133,
|
||||
33 -> -125,
|
||||
34 -> -117,
|
||||
35 -> -110,
|
||||
36 -> -102,
|
||||
37 -> -95,
|
||||
38 -> -87,
|
||||
39 -> -80,
|
||||
40 -> -72,
|
||||
41 -> -65,
|
||||
42 -> -57,
|
||||
43 -> -50,
|
||||
44 -> -43,
|
||||
45 -> -36,
|
||||
46 -> -29,
|
||||
47 -> -21,
|
||||
48 -> -14,
|
||||
49 -> -7,
|
||||
50 -> 0,
|
||||
51 -> 7,
|
||||
52 -> 14,
|
||||
53 -> 21,
|
||||
54 -> 29,
|
||||
55 -> 36,
|
||||
56 -> 43,
|
||||
57 -> 50,
|
||||
58 -> 57,
|
||||
59 -> 65,
|
||||
60 -> 72,
|
||||
61 -> 80,
|
||||
62 -> 87,
|
||||
63 -> 95,
|
||||
64 -> 102,
|
||||
65 -> 110,
|
||||
66 -> 117,
|
||||
67 -> 125,
|
||||
68 -> 133,
|
||||
69 -> 141,
|
||||
70 -> 149,
|
||||
71 -> 158,
|
||||
72 -> 166,
|
||||
73 -> 175,
|
||||
74 -> 184,
|
||||
75 -> 193,
|
||||
76 -> 202,
|
||||
77 -> 211,
|
||||
78 -> 220,
|
||||
79 -> 230,
|
||||
80 -> 240,
|
||||
81 -> 251,
|
||||
82 -> 262,
|
||||
83 -> 273,
|
||||
84 -> 284,
|
||||
85 -> 296,
|
||||
86 -> 309,
|
||||
87 -> 322,
|
||||
88 -> 336,
|
||||
89 -> 351,
|
||||
90 -> 366,
|
||||
91 -> 383,
|
||||
92 -> 401,
|
||||
93 -> 422,
|
||||
94 -> 444,
|
||||
95 -> 470,
|
||||
96 -> 501,
|
||||
97 -> 538,
|
||||
98 -> 589,
|
||||
99 -> 677,
|
||||
0 -> -800,
|
||||
1 -> -677,
|
||||
2 -> -589,
|
||||
3 -> -538,
|
||||
4 -> -501,
|
||||
5 -> -470,
|
||||
6 -> -444,
|
||||
7 -> -422,
|
||||
8 -> -401,
|
||||
9 -> -383,
|
||||
10 -> -366,
|
||||
11 -> -351,
|
||||
12 -> -336,
|
||||
13 -> -322,
|
||||
14 -> -309,
|
||||
15 -> -296,
|
||||
16 -> -284,
|
||||
17 -> -273,
|
||||
18 -> -262,
|
||||
19 -> -251,
|
||||
20 -> -240,
|
||||
21 -> -230,
|
||||
22 -> -220,
|
||||
23 -> -211,
|
||||
24 -> -202,
|
||||
25 -> -193,
|
||||
26 -> -184,
|
||||
27 -> -175,
|
||||
28 -> -166,
|
||||
29 -> -158,
|
||||
30 -> -149,
|
||||
31 -> -141,
|
||||
32 -> -133,
|
||||
33 -> -125,
|
||||
34 -> -117,
|
||||
35 -> -110,
|
||||
36 -> -102,
|
||||
37 -> -95,
|
||||
38 -> -87,
|
||||
39 -> -80,
|
||||
40 -> -72,
|
||||
41 -> -65,
|
||||
42 -> -57,
|
||||
43 -> -50,
|
||||
44 -> -43,
|
||||
45 -> -36,
|
||||
46 -> -29,
|
||||
47 -> -21,
|
||||
48 -> -14,
|
||||
49 -> -7,
|
||||
50 -> 0,
|
||||
51 -> 7,
|
||||
52 -> 14,
|
||||
53 -> 21,
|
||||
54 -> 29,
|
||||
55 -> 36,
|
||||
56 -> 43,
|
||||
57 -> 50,
|
||||
58 -> 57,
|
||||
59 -> 65,
|
||||
60 -> 72,
|
||||
61 -> 80,
|
||||
62 -> 87,
|
||||
63 -> 95,
|
||||
64 -> 102,
|
||||
65 -> 110,
|
||||
66 -> 117,
|
||||
67 -> 125,
|
||||
68 -> 133,
|
||||
69 -> 141,
|
||||
70 -> 149,
|
||||
71 -> 158,
|
||||
72 -> 166,
|
||||
73 -> 175,
|
||||
74 -> 184,
|
||||
75 -> 193,
|
||||
76 -> 202,
|
||||
77 -> 211,
|
||||
78 -> 220,
|
||||
79 -> 230,
|
||||
80 -> 240,
|
||||
81 -> 251,
|
||||
82 -> 262,
|
||||
83 -> 273,
|
||||
84 -> 284,
|
||||
85 -> 296,
|
||||
86 -> 309,
|
||||
87 -> 322,
|
||||
88 -> 336,
|
||||
89 -> 351,
|
||||
90 -> 366,
|
||||
91 -> 383,
|
||||
92 -> 401,
|
||||
93 -> 422,
|
||||
94 -> 444,
|
||||
95 -> 470,
|
||||
96 -> 501,
|
||||
97 -> 538,
|
||||
98 -> 589,
|
||||
99 -> 677,
|
||||
100 -> 800
|
||||
)
|
||||
|
||||
@@ -16,8 +16,8 @@ final class GlickoCalculator(
|
||||
|
||||
// Simpler use case: a single game
|
||||
def computeGame(game: Game, skipDeviationIncrease: Boolean = false): Try[ByColor[Player]] =
|
||||
val ratings = game.players.map(conversions.toRating)
|
||||
val gameResult = conversions.toGameResult(ratings, game.outcome)
|
||||
val ratings = game.players.map(conversions.toRating)
|
||||
val gameResult = conversions.toGameResult(ratings, game.outcome)
|
||||
val periodResults = impl.GameRatingPeriodResults(List(gameResult))
|
||||
Try:
|
||||
calculator.updateRatings(periodResults, skipDeviationIncrease)
|
||||
@@ -39,7 +39,7 @@ final class GlickoCalculator(
|
||||
|
||||
def toGameResult(ratings: ByColor[Rating], outcome: Outcome): GameResult =
|
||||
outcome.winner match
|
||||
case None => GameResult(ratings.white, ratings.black, true)
|
||||
case None => GameResult(ratings.white, ratings.black, true)
|
||||
case Some(White) => GameResult(ratings.white, ratings.black, false)
|
||||
case Some(Black) => GameResult(ratings.black, ratings.white, false)
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ final private[glicko] class Rating(
|
||||
import RatingCalculator.*
|
||||
|
||||
// the following variables are used to hold values temporarily whilst running calculations
|
||||
private[impl] var workingRating: Double = scala.compiletime.uninitialized
|
||||
private[impl] var workingRating: Double = scala.compiletime.uninitialized
|
||||
private[impl] var workingRatingDeviation: Double = scala.compiletime.uninitialized
|
||||
private[impl] var workingVolatility: Double = scala.compiletime.uninitialized
|
||||
private[impl] var workingVolatility: Double = scala.compiletime.uninitialized
|
||||
|
||||
/** Return the average skill value of the player scaled down to the scale used by the algorithm's internal
|
||||
* workings.
|
||||
|
||||
@@ -8,7 +8,7 @@ import java.time.Instant
|
||||
private object RatingCalculator:
|
||||
|
||||
private val MULTIPLIER: Double = 173.7178
|
||||
val DEFAULT_RATING: Double = 1500.0
|
||||
val DEFAULT_RATING: Double = 1500.0
|
||||
|
||||
def convertRatingToOriginalGlickoScale(rating: Double): Double =
|
||||
((rating * MULTIPLIER) + DEFAULT_RATING)
|
||||
@@ -30,8 +30,8 @@ final private[glicko] class RatingCalculator(
|
||||
import RatingCalculator.*
|
||||
|
||||
private val CONVERGENCE_TOLERANCE: Double = 0.000001
|
||||
private val ITERATION_MAX: Int = 1000
|
||||
private val DAYS_PER_MILLI: Double = 1.0 / (1000 * 60 * 60 * 24)
|
||||
private val ITERATION_MAX: Int = 1000
|
||||
private val DAYS_PER_MILLI: Double = 1.0 / (1000 * 60 * 60 * 24)
|
||||
|
||||
private val ratingPeriodsPerMilli: Double = ratingPeriodsPerDay.value * DAYS_PER_MILLI
|
||||
|
||||
@@ -86,12 +86,12 @@ final private[glicko] class RatingCalculator(
|
||||
* @param elapsedRatingPeriods
|
||||
*/
|
||||
private def calculateNewRating(player: Rating, results: List[Result], elapsedRatingPeriods: Double): Unit =
|
||||
val phi = player.getGlicko2RatingDeviation
|
||||
val phi = player.getGlicko2RatingDeviation
|
||||
val sigma = player.volatility
|
||||
val a = Math.log(Math.pow(sigma, 2))
|
||||
val a = Math.log(Math.pow(sigma, 2))
|
||||
val delta = deltaOf(player, results)
|
||||
val v = vOf(player, results)
|
||||
val tau = this.tau.value
|
||||
val v = vOf(player, results)
|
||||
val tau = this.tau.value
|
||||
|
||||
// step 5.2 - set the initial values of the iterative algorithm to come in step 5.4
|
||||
var A: Double = a
|
||||
@@ -114,7 +114,7 @@ final private[glicko] class RatingCalculator(
|
||||
while Math.abs(B - A) > CONVERGENCE_TOLERANCE && iterations < ITERATION_MAX do
|
||||
iterations = iterations + 1
|
||||
// println(String.format("%f - %f (%f) > %f", B, A, Math.abs(B - A), CONVERGENCE_TOLERANCE))
|
||||
val C = A + (((A - B) * fA) / (fB - fA))
|
||||
val C = A + (((A - B) * fA) / (fB - fA))
|
||||
val fC = f(C, delta, phi, v, a, tau)
|
||||
|
||||
if fC * fB <= 0 then
|
||||
|
||||
@@ -23,7 +23,7 @@ final private[glicko] class FloatingResult(player: Rating, opponent: Rating, sco
|
||||
def players = List(player, opponent)
|
||||
|
||||
final private[glicko] class GameResult(winner: Rating, loser: Rating, isDraw: Boolean) extends Result:
|
||||
private val POINTS_FOR_WIN = 1.0d
|
||||
private val POINTS_FOR_WIN = 1.0d
|
||||
private val POINTS_FOR_LOSS = 0.0d
|
||||
private val POINTS_FOR_DRAW = 0.5d
|
||||
|
||||
@@ -54,7 +54,7 @@ final private[glicko] class GameResult(winner: Rating, loser: Rating, isDraw: Bo
|
||||
private[glicko] trait RatingPeriodResults[R <: Result]():
|
||||
val results: List[R]
|
||||
def getResults(player: Rating): List[R] = results.filter(_.participated(player))
|
||||
def getParticipants: Set[Rating] = results.flatMap(_.players).toSet
|
||||
def getParticipants: Set[Rating] = results.flatMap(_.players).toSet
|
||||
|
||||
final private[glicko] class GameRatingPeriodResults(val results: List[GameResult])
|
||||
extends RatingPeriodResults[GameResult]
|
||||
|
||||
@@ -12,12 +12,12 @@ case class Glicko(
|
||||
volatility: Double
|
||||
):
|
||||
def intRating: IntRating = IntRating(rating.toInt)
|
||||
def intDeviation = deviation.toInt
|
||||
def provisional = RatingProvisional(deviation >= provisionalDeviation)
|
||||
def established = provisional.no
|
||||
def intDeviation = deviation.toInt
|
||||
def provisional = RatingProvisional(deviation >= provisionalDeviation)
|
||||
def established = provisional.no
|
||||
def establishedIntRating = Option.when(established)(intRating)
|
||||
def clueless = deviation >= cluelessDeviation
|
||||
def display = s"$intRating${if provisional.yes then "?" else ""}"
|
||||
def clueless = deviation >= cluelessDeviation
|
||||
def display = s"$intRating${if provisional.yes then "?" else ""}"
|
||||
def average(other: Glicko, weight: Float = 0.5f): Glicko =
|
||||
if weight >= 1 then other
|
||||
else if weight <= 0 then this
|
||||
@@ -30,7 +30,7 @@ case class Glicko(
|
||||
override def toString = f"$intRating/$intDeviation/${volatility}%.3f"
|
||||
|
||||
val provisionalDeviation = 110
|
||||
val cluelessDeviation = 230
|
||||
val cluelessDeviation = 230
|
||||
|
||||
case class Player(
|
||||
glicko: Glicko,
|
||||
|
||||
@@ -8,7 +8,7 @@ object IntRatingDiff extends RichOpaqueInt[IntRatingDiff]:
|
||||
extension (diff: IntRatingDiff)
|
||||
def positive: Boolean = diff > 0
|
||||
def negative: Boolean = diff < 0
|
||||
def zero: Boolean = diff == 0
|
||||
def zero: Boolean = diff == 0
|
||||
given Zero[IntRatingDiff] = Zero(0)
|
||||
|
||||
opaque type Rating = Double
|
||||
|
||||
@@ -20,7 +20,7 @@ object ChessTreeArbitraries:
|
||||
if seed.end then Gen.const(LazyList(seed))
|
||||
else
|
||||
for
|
||||
board <- Gen.oneOf(seed.legalMoves.map(_.after))
|
||||
board <- Gen.oneOf(seed.legalMoves.map(_.after))
|
||||
boards <- genBoards(board)
|
||||
yield board #:: boards
|
||||
|
||||
@@ -43,10 +43,10 @@ object ChessTreeArbitraries:
|
||||
else
|
||||
val nextSeeds = seed.legalMoves
|
||||
for
|
||||
value <- Gen.oneOf(nextSeeds)
|
||||
withMove <- value.next(none)
|
||||
value <- Gen.oneOf(nextSeeds)
|
||||
withMove <- value.next(none)
|
||||
variations <- nextSeeds.filter(_ != value).traverse(_.next(none))
|
||||
node <- genNode(withMove, variations)
|
||||
node <- genNode(withMove, variations)
|
||||
yield node.some
|
||||
|
||||
def genNodeWithPath[A](seed: Position)(using FromMove[A]): Gen[(Option[Node[WithMove[A]]], List[A])] =
|
||||
@@ -54,17 +54,17 @@ object ChessTreeArbitraries:
|
||||
else
|
||||
val nextSeeds = seed.legalMoves
|
||||
for
|
||||
value <- Gen.oneOf(nextSeeds)
|
||||
withMove <- value.next(none)
|
||||
value <- Gen.oneOf(nextSeeds)
|
||||
withMove <- value.next(none)
|
||||
variations <- nextSeeds.filter(_ != value).traverse(_.next(none))
|
||||
node <- genNode(withMove, variations)
|
||||
path <- NodeArbitraries.genPath(node).map(_.map(_.data))
|
||||
node <- genNode(withMove, variations)
|
||||
path <- NodeArbitraries.genPath(node).map(_.map(_.data))
|
||||
yield (node.some, path)
|
||||
|
||||
def genComments(size: Int) =
|
||||
for
|
||||
commentSize <- Gen.choose(0, size)
|
||||
xs <- Gen.listOfN(commentSize, Gen.alphaStr)
|
||||
xs <- Gen.listOfN(commentSize, Gen.alphaStr)
|
||||
comments = xs.collect { case s if s.nonEmpty => Comment(s) }
|
||||
yield comments
|
||||
|
||||
@@ -79,7 +79,7 @@ object ChessTreeArbitraries:
|
||||
def next: Gen[List[WithMove[A]]] =
|
||||
for
|
||||
variations <- pickSome(move.move.after.legalMoves)
|
||||
nextMoves <- variations.traverse(_.next(move.data.some))
|
||||
nextMoves <- variations.traverse(_.next(move.data.some))
|
||||
yield nextMoves
|
||||
|
||||
given FromMove[PgnMove] with
|
||||
@@ -87,8 +87,8 @@ object ChessTreeArbitraries:
|
||||
def next(m: Option[PgnMove]): Gen[WithMove[PgnMove]] =
|
||||
for
|
||||
comments <- genComments(5)
|
||||
glyphs <- Gen.someOf(Glyphs.all).map(xs => Glyphs.fromList(xs.toList))
|
||||
clock <- Gen.posNum[Int]
|
||||
glyphs <- Gen.someOf(Glyphs.all).map(xs => Glyphs.fromList(xs.toList))
|
||||
clock <- Gen.posNum[Int]
|
||||
yield WithMove(move, PgnMove(move.toSanStr, comments, glyphs, timeLeft = Seconds(clock).some))
|
||||
|
||||
def genNode[A: Generator](value: A, variations: List[A] = Nil): Gen[Node[A]] =
|
||||
|
||||
@@ -6,32 +6,32 @@ import chess.variant.{ Crazyhouse, Variant }
|
||||
import org.scalacheck.{ Arbitrary, Cogen, Gen }
|
||||
|
||||
object CoreArbitraries:
|
||||
given Arbitrary[Color] = Arbitrary(Gen.oneOf(Color.all))
|
||||
given Arbitrary[Side] = Arbitrary(Gen.oneOf(Side.all))
|
||||
given Arbitrary[Role] = Arbitrary(Gen.oneOf(Role.all))
|
||||
given Arbitrary[File] = Arbitrary(Gen.oneOf(File.all))
|
||||
given Arbitrary[Rank] = Arbitrary(Gen.oneOf(Rank.all))
|
||||
given Arbitrary[Square] = Arbitrary(Gen.oneOf(Square.all))
|
||||
given Arbitrary[Color] = Arbitrary(Gen.oneOf(Color.all))
|
||||
given Arbitrary[Side] = Arbitrary(Gen.oneOf(Side.all))
|
||||
given Arbitrary[Role] = Arbitrary(Gen.oneOf(Role.all))
|
||||
given Arbitrary[File] = Arbitrary(Gen.oneOf(File.all))
|
||||
given Arbitrary[Rank] = Arbitrary(Gen.oneOf(Rank.all))
|
||||
given Arbitrary[Square] = Arbitrary(Gen.oneOf(Square.all))
|
||||
given Arbitrary[Variant] = Arbitrary(Gen.oneOf(Variant.list.all))
|
||||
given Arbitrary[Glyph] = Arbitrary(Gen.oneOf(Glyphs.all))
|
||||
given Arbitrary[Glyphs] = Arbitrary:
|
||||
given Arbitrary[Glyph] = Arbitrary(Gen.oneOf(Glyphs.all))
|
||||
given Arbitrary[Glyphs] = Arbitrary:
|
||||
Gen.listOf(Arbitrary.arbitrary[Glyph]).map(Glyphs.fromList)
|
||||
given Arbitrary[Centis] = Arbitrary(Gen.posNum[Int].map(Centis(_)))
|
||||
|
||||
given Arbitrary[Piece] = Arbitrary:
|
||||
for
|
||||
color <- Arbitrary.arbitrary[Color]
|
||||
role <- Arbitrary.arbitrary[Role]
|
||||
role <- Arbitrary.arbitrary[Role]
|
||||
yield Piece(color, role)
|
||||
|
||||
given Arbitrary[Castles] = Arbitrary(castlesGen)
|
||||
given Arbitrary[Castles] = Arbitrary(castlesGen)
|
||||
given Arbitrary[Bitboard] = Arbitrary(Gen.long.map(Bitboard(_)))
|
||||
|
||||
given Arbitrary[PromotableRole] = Arbitrary(Gen.oneOf(Rook, Knight, Bishop, Queen))
|
||||
|
||||
given Arbitrary[Uci] = Arbitrary(Gen.oneOf(normalUciMoveGen, promotionUciMoveGen, dropUciMoveGen))
|
||||
|
||||
given Cogen[Color] = Cogen.cogenBoolean.contramap(_.white)
|
||||
given Cogen[Color] = Cogen.cogenBoolean.contramap(_.white)
|
||||
given Cogen[Square] = Cogen(_.value.toLong)
|
||||
given Cogen[Centis] = Cogen(_.value.toLong)
|
||||
|
||||
@@ -43,16 +43,16 @@ object CoreArbitraries:
|
||||
|
||||
given Arbitrary[Crazyhouse.Pocket] = Arbitrary:
|
||||
for
|
||||
pawn <- Gen.oneOf(0 to 8)
|
||||
pawn <- Gen.oneOf(0 to 8)
|
||||
knight <- Gen.oneOf(0 to 2)
|
||||
bishop <- Gen.oneOf(0 to 2)
|
||||
rook <- Gen.oneOf(0 to 2)
|
||||
queen <- Gen.oneOf(0 to 2)
|
||||
rook <- Gen.oneOf(0 to 2)
|
||||
queen <- Gen.oneOf(0 to 2)
|
||||
yield Crazyhouse.Pocket(pawn, knight, bishop, rook, queen)
|
||||
|
||||
given Arbitrary[Crazyhouse.Data] = Arbitrary:
|
||||
for
|
||||
pockets <- Arbitrary.arbitrary[ByColor[Crazyhouse.Pocket]]
|
||||
pockets <- Arbitrary.arbitrary[ByColor[Crazyhouse.Pocket]]
|
||||
promoted <- Arbitrary.arbitrary[Bitboard]
|
||||
yield Crazyhouse.Data(pockets, promoted)
|
||||
|
||||
@@ -64,13 +64,13 @@ object CoreArbitraries:
|
||||
|
||||
def promotionUciMoveGen =
|
||||
for
|
||||
file <- Arbitrary.arbitrary[File]
|
||||
rank <- Gen.oneOf(Rank.Second, Rank.Seventh)
|
||||
role <- Gen.oneOf(Role.allPromotable)
|
||||
file <- Arbitrary.arbitrary[File]
|
||||
rank <- Gen.oneOf(Rank.Second, Rank.Seventh)
|
||||
role <- Gen.oneOf(Role.allPromotable)
|
||||
offset <- Gen.oneOf(-1, 1)
|
||||
destFile = File(file.value + offset).getOrElse(file)
|
||||
orig = Square(file, rank)
|
||||
dest = Square(destFile, UciCharPair.implementation.lastRank(orig))
|
||||
orig = Square(file, rank)
|
||||
dest = Square(destFile, UciCharPair.implementation.lastRank(orig))
|
||||
yield Uci.Move(orig, dest, Some(role))
|
||||
|
||||
def dropUciMoveGen =
|
||||
@@ -79,7 +79,7 @@ object CoreArbitraries:
|
||||
role <- Gen.oneOf(Pawn, Knight, Bishop, Rook, Queen)
|
||||
yield Uci.Drop(role, dest)
|
||||
|
||||
private val genBool = Gen.prob(0.5)
|
||||
private val genBool = Gen.prob(0.5)
|
||||
private val castlesGen =
|
||||
for
|
||||
wks <- genBool
|
||||
|
||||
@@ -5,15 +5,15 @@ import org.scalacheck.{ Arbitrary, Gen }
|
||||
|
||||
object NodeArbitraries:
|
||||
|
||||
given [A](using Arbitrary[A]): Arbitrary[Tree[A]] = Arbitrary(Gen.oneOf(genNode, genVariation))
|
||||
given [A](using Arbitrary[A]): Arbitrary[Node[A]] = Arbitrary(genNode)
|
||||
given [A](using Arbitrary[A]): Arbitrary[Tree[A]] = Arbitrary(Gen.oneOf(genNode, genVariation))
|
||||
given [A](using Arbitrary[A]): Arbitrary[Node[A]] = Arbitrary(genNode)
|
||||
given [A](using Arbitrary[A]): Arbitrary[Variation[A]] = Arbitrary(genVariation)
|
||||
|
||||
type NodeWithPath[A] = (Node[A], List[A])
|
||||
given [A](using Arbitrary[A]): Arbitrary[NodeWithPath[A]] = Arbitrary(genNodeWithPath)
|
||||
|
||||
given treeEq[A]: Eq[Tree[A]] = Eq.fromUniversalEquals
|
||||
given nodeEq[A]: Eq[Node[A]] = Eq.fromUniversalEquals
|
||||
given treeEq[A]: Eq[Tree[A]] = Eq.fromUniversalEquals
|
||||
given nodeEq[A]: Eq[Node[A]] = Eq.fromUniversalEquals
|
||||
given variationEq[A]: Eq[Variation[A]] = Eq.fromUniversalEquals
|
||||
|
||||
def genNodeWithPath[A](using Arbitrary[A]) =
|
||||
@@ -27,7 +27,7 @@ object NodeArbitraries:
|
||||
Gen
|
||||
.prob(prob)
|
||||
.flatMap:
|
||||
case true => node.child.fold(Gen.const(Nil))(genPath(_)).map(node.value :: _)
|
||||
case true => node.child.fold(Gen.const(Nil))(genPath(_)).map(node.value :: _)
|
||||
case false =>
|
||||
if node.variations.isEmpty
|
||||
then Gen.const(Nil)
|
||||
@@ -35,7 +35,7 @@ object NodeArbitraries:
|
||||
Gen
|
||||
.prob(0.95)
|
||||
.flatMap:
|
||||
case true => Gen.oneOf(node.variations).flatMap(v => genPath(v.toNode))
|
||||
case true => Gen.oneOf(node.variations).flatMap(v => genPath(v.toNode))
|
||||
case false => Gen.const(node.value :: Nil)
|
||||
|
||||
def genNode[A](using Arbitrary[A]): Gen[Node[A]] =
|
||||
|
||||
@@ -16,11 +16,11 @@ object macros:
|
||||
object PgnLiteral extends Literally[ParsedPgn]:
|
||||
def validate(s: String)(using Quotes) =
|
||||
Parser.full(PgnStr(s)) match
|
||||
case Right(_) => Right('{ Parser.full(PgnStr(${ Expr(s) })).toOption.get })
|
||||
case Right(_) => Right('{ Parser.full(PgnStr(${ Expr(s) })).toOption.get })
|
||||
case Left(err) => Left(err.toString)
|
||||
|
||||
object UciLiteral extends Literally[Uci]:
|
||||
def validate(s: String)(using Quotes) =
|
||||
Uci(s) match
|
||||
case Some(_) => Right('{ Uci(${ Expr(s) }).get })
|
||||
case _ => Left(s"Invalid UCI: $s")
|
||||
case _ => Left(s"Invalid UCI: $s")
|
||||
|
||||
@@ -52,14 +52,14 @@ g4 {[%emt 0.200]} 34. Rxg4 {[%emt 0.172]} 0-1"""
|
||||
|
||||
test("Allow an opening move for white taking into account a player may move without taking if possible"):
|
||||
val startingPosition = Game(Antichess)
|
||||
val newGame = startingPosition.playMove(Square.E2, Square.E4, None).get
|
||||
val fen = Fen.write(newGame)
|
||||
val newGame = startingPosition.playMove(Square.E2, Square.E4, None).get
|
||||
val fen = Fen.write(newGame)
|
||||
assertEquals(fen, FullFen("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b - - 0 1"))
|
||||
|
||||
test("Not allow a player to make a non capturing move if a capturing move is available"):
|
||||
val game = Game(Antichess)
|
||||
val game = Game(Antichess)
|
||||
val gameAfterOpening = game.playMoves((Square.E2, Square.E4), (Square.F7, Square.F5))
|
||||
val invalidGame = gameAfterOpening.flatMap(_.playMove(Square.H2, Square.H4))
|
||||
val invalidGame = gameAfterOpening.flatMap(_.playMove(Square.H2, Square.H4))
|
||||
assertEquals(invalidGame, Left(ErrorStr("Piece on h2 cannot move to h4")))
|
||||
|
||||
test("A board in antichess should only present the capturing moves if the player can capture"):
|
||||
@@ -120,18 +120,18 @@ g4 {[%emt 0.200]} 34. Rxg4 {[%emt 0.172]} 0-1"""
|
||||
|
||||
test("Allow a pawn to be promoted to a king"):
|
||||
val position = FullFen("8/5P2/8/2b5/8/8/4B3/8 w - -")
|
||||
val game = fenToGame(position, Antichess)
|
||||
val newGame = game(Square.F7, Square.F8, Option(King)).get._1
|
||||
val game = fenToGame(position, Antichess)
|
||||
val newGame = game(Square.F7, Square.F8, Option(King)).get._1
|
||||
assertEquals(newGame.position.pieceAt(Square.F8), Option(White - King))
|
||||
|
||||
test("deal with 2 white kings"):
|
||||
val position = FullFen("K3k1nr/p2q2pp/p2p1p2/8/2PP4/8/PP4PP/RNBQK1NR w - - 0 11")
|
||||
val game = fenToGame(position, Antichess)
|
||||
val game = fenToGame(position, Antichess)
|
||||
assertEquals(game.position.destinations, Map(Square.A8 -> Square.A7.bb))
|
||||
|
||||
test("Be drawn when there are only opposite colour bishops remaining"):
|
||||
val position = FullFen("8/2b5/8/8/8/6Q1/4B3/8 b - -")
|
||||
val game = fenToGame(position, Antichess)(Square.C7, Square.G3, None).get._1
|
||||
val game = fenToGame(position, Antichess)(Square.C7, Square.G3, None).get._1
|
||||
assert(game.position.end)
|
||||
assert(game.position.autoDraw)
|
||||
assertEquals(game.position.winner, None)
|
||||
@@ -139,7 +139,7 @@ g4 {[%emt 0.200]} 34. Rxg4 {[%emt 0.172]} 0-1"""
|
||||
|
||||
test("Be drawn on multiple bishops on the opposite color"):
|
||||
val position = FullFen("8/6P1/8/8/1b6/8/8/5B2 w - -")
|
||||
val game = fenToGame(position, Antichess)(Square.G7, Square.G8, Bishop.some).get._1
|
||||
val game = fenToGame(position, Antichess)(Square.G7, Square.G8, Bishop.some).get._1
|
||||
assert(game.position.end)
|
||||
assert(game.position.autoDraw)
|
||||
assertEquals(game.position.winner, None)
|
||||
@@ -147,7 +147,7 @@ g4 {[%emt 0.200]} 34. Rxg4 {[%emt 0.172]} 0-1"""
|
||||
|
||||
test("Not be drawn when the black and white bishops are on the same coloured squares "):
|
||||
val position = FullFen("7b/8/1p6/8/8/8/5B2/8 w - -")
|
||||
val game = fenToGame(position, Antichess)(Square.F2, Square.B6, None).get._1
|
||||
val game = fenToGame(position, Antichess)(Square.F2, Square.B6, None).get._1
|
||||
assertNot(game.position.end)
|
||||
assertNot(game.position.autoDraw)
|
||||
assertEquals(game.position.winner, None)
|
||||
@@ -156,69 +156,69 @@ g4 {[%emt 0.200]} 34. Rxg4 {[%emt 0.172]} 0-1"""
|
||||
"Be drawn when there are only opposite colour bishops and pawns which could not attack those bishops remaining"
|
||||
):
|
||||
val position = FullFen("8/6p1/4B1P1/4p3/4P3/8/2p5/8 b - - 1 28")
|
||||
val game = fenToGame(position, Antichess)(Square.C2, Square.C1, Option(Bishop)).get._1
|
||||
val game = fenToGame(position, Antichess)(Square.C2, Square.C1, Option(Bishop)).get._1
|
||||
assert(game.position.end)
|
||||
assert(game.position.autoDraw)
|
||||
assertEquals(game.position.status, Some(Status.Draw))
|
||||
|
||||
test("Not be drawn on opposite color bishops but with pawns that could be forced to attack a bishop"):
|
||||
val position = FullFen("8/6p1/1B4P1/4p3/4P3/8/3p4/8 b - -")
|
||||
val game = fenToGame(position, Antichess)(Square.D2, Square.D1, Option(Bishop)).get._1
|
||||
val game = fenToGame(position, Antichess)(Square.D2, Square.D1, Option(Bishop)).get._1
|
||||
assertNot(game.position.end)
|
||||
assertNot(game.position.autoDraw)
|
||||
assertEquals(game.position.winner, None)
|
||||
|
||||
test("Not be drawn where a white bishop can attack a black pawn in an almost closed position"):
|
||||
val position = FullFen("5b2/1P4p1/4B1P1/4p3/4P3/8/8/8 w - -")
|
||||
val game = fenToGame(position, Antichess)(Square.B7, Square.B8, Bishop.some).get._1
|
||||
val game = fenToGame(position, Antichess)(Square.B7, Square.B8, Bishop.some).get._1
|
||||
assertNot(game.position.end)
|
||||
assertNot(game.position.autoDraw)
|
||||
assertEquals(game.position.winner, None)
|
||||
|
||||
test("Not be drawn where a pawn is unattackable, but is blocked by a bishop, not a pawn"):
|
||||
val position = FullFen("8/8/4BbP1/4p3/4P3/8/8/8 b - -")
|
||||
val game = fenToGame(position, Antichess).playMoves(Square.F6 -> Square.G7).get
|
||||
val game = fenToGame(position, Antichess).playMoves(Square.F6 -> Square.G7).get
|
||||
assertNot(game.position.end)
|
||||
assertNot(game.position.autoDraw)
|
||||
assertEquals(game.position.status, None)
|
||||
|
||||
test("Opponent has insufficient material when there are only two remaining knights on same color squares"):
|
||||
val position = FullFen("8/8/3n2N1/8/8/8/8/8 w - -")
|
||||
val game = fenToGame(position, Antichess).playMoves(Square.G6 -> Square.F4).get
|
||||
val game = fenToGame(position, Antichess).playMoves(Square.G6 -> Square.F4).get
|
||||
assert(game.position.opponentHasInsufficientMaterial)
|
||||
|
||||
test(
|
||||
"Opponent has sufficient material when there are only two remaining knights on opposite color squares"
|
||||
):
|
||||
val position = FullFen("7n/8/8/8/8/8/8/N7 w - -")
|
||||
val game = fenToGame(position, Antichess).playMoves(Square.A1 -> Square.B3).get
|
||||
val game = fenToGame(position, Antichess).playMoves(Square.A1 -> Square.B3).get
|
||||
assertNot(game.position.opponentHasInsufficientMaterial)
|
||||
|
||||
test(
|
||||
"Player has insufficient material when there are only two remaining knights on opposite color squares"
|
||||
):
|
||||
val position = FullFen("1n6/8/8/8/8/4N3/8/8 w - - 0 1")
|
||||
val game = fenToGame(position, Antichess).playMoves(Square.E3 -> Square.D1).get
|
||||
val game = fenToGame(position, Antichess).playMoves(Square.E3 -> Square.D1).get
|
||||
assertEquals(game.position.playerHasInsufficientMaterial, Some(true))
|
||||
|
||||
test(
|
||||
"Player has sufficient material when there are only two remaining knights on same color squares"
|
||||
):
|
||||
val position = FullFen("1n6/8/8/8/8/8/8/7N w - - 0 1")
|
||||
val game = fenToGame(position, Antichess).playMoves(Square.H1 -> Square.G3).get
|
||||
val game = fenToGame(position, Antichess).playMoves(Square.H1 -> Square.G3).get
|
||||
assertEquals(game.position.playerHasInsufficientMaterial, Some(false))
|
||||
|
||||
test("Not be drawn on insufficient mating material"):
|
||||
val position = FullFen("4K3/8/1b6/8/8/8/5B2/3k4 b - -")
|
||||
val game = fenToGame(position, Antichess)
|
||||
val game = fenToGame(position, Antichess)
|
||||
assertNot(game.position.end)
|
||||
|
||||
test("Be drawn on a three move repetition"):
|
||||
val game = Game(Antichess)
|
||||
val game = Game(Antichess)
|
||||
val moves =
|
||||
List((Square.G1, Square.F3), (Square.G8, Square.F6), (Square.F3, Square.G1), (Square.F6, Square.G8))
|
||||
val repeatedMoves: List[(Square, Square)] = List.fill(3)(moves).flatten
|
||||
val g = game.playMoveList(repeatedMoves).get
|
||||
val g = game.playMoveList(repeatedMoves).get
|
||||
assert(g.position.threefoldRepetition)
|
||||
|
||||
test("Successfully play through a full game until one player loses all their pieces"):
|
||||
@@ -233,12 +233,12 @@ g4 {[%emt 0.200]} 34. Rxg4 {[%emt 0.172]} 0-1"""
|
||||
|
||||
test("Win on a traditional stalemate where the player has no valid moves"):
|
||||
val position = FullFen("8/p7/8/P7/8/8/8/8 w - -")
|
||||
val game = fenToGame(position, Antichess).playMoves(Square.A5 -> Square.A6).get
|
||||
val game = fenToGame(position, Antichess).playMoves(Square.A5 -> Square.A6).get
|
||||
assert(game.position.end)
|
||||
assertEquals(game.position.winner, Some(Black))
|
||||
|
||||
test("Stalemate is a win - second test"):
|
||||
val fen = FullFen("2Q5/8/p7/8/8/8/6PR/8 w - -")
|
||||
val fen = FullFen("2Q5/8/p7/8/8/8/6PR/8 w - -")
|
||||
val game = fenToGame(fen, Antichess).playMoves(Square.C8 -> Square.A6).get
|
||||
assert(game.position.end)
|
||||
assertEquals(game.position.status, Some(Status.VariantEnd))
|
||||
|
||||
@@ -8,10 +8,10 @@ import chess.variant.Atomic
|
||||
class AtomicVariantTest extends ChessTest:
|
||||
|
||||
test("Must explode surrounding non pawn pieces on capture"):
|
||||
val fenPosition = FullFen("rnbqkbnr/1ppppp1p/p5p1/8/8/1P6/PBPPPPPP/RN1QKBNR w KQkq -")
|
||||
val game = fenToGame(fenPosition, Atomic)
|
||||
val fenPosition = FullFen("rnbqkbnr/1ppppp1p/p5p1/8/8/1P6/PBPPPPPP/RN1QKBNR w KQkq -")
|
||||
val game = fenToGame(fenPosition, Atomic)
|
||||
val explodedSquares = List(Square.H8, Square.G8)
|
||||
val intactPawns = List(Square.F7, Square.G6, Square.H7)
|
||||
val intactPawns = List(Square.F7, Square.G6, Square.H7)
|
||||
|
||||
game
|
||||
.playMoves((Square.B2, Square.H8))
|
||||
@@ -20,8 +20,8 @@ class AtomicVariantTest extends ChessTest:
|
||||
assert(intactPawns.forall(square => game.position.pieceAt(square).isDefined))
|
||||
|
||||
test("Must explode all surrounding non pawn pieces on capture (contrived board)"):
|
||||
val fenPosition = FullFen("k7/3bbn2/3rqn2/3qr3/8/7B/8/1K6 w - -")
|
||||
val game = fenToGame(fenPosition, Atomic)
|
||||
val fenPosition = FullFen("k7/3bbn2/3rqn2/3qr3/8/7B/8/1K6 w - -")
|
||||
val game = fenToGame(fenPosition, Atomic)
|
||||
val explodedSquares =
|
||||
List(Square.D5, Square.E5, Square.D6, Square.E6, Square.F6, Square.D7, Square.E7, Square.F7)
|
||||
|
||||
@@ -33,8 +33,8 @@ class AtomicVariantTest extends ChessTest:
|
||||
test(
|
||||
"Must explode all surrounding non pawn pieces on capture (contrived board with bottom right position)"
|
||||
):
|
||||
val fenPosition = FullFen("k7/3bbn2/3rqn2/4rq2/8/1B6/8/K7 w - -")
|
||||
val game = fenToGame(fenPosition, Atomic)
|
||||
val fenPosition = FullFen("k7/3bbn2/3rqn2/4rq2/8/1B6/8/K7 w - -")
|
||||
val game = fenToGame(fenPosition, Atomic)
|
||||
val explodedSquares =
|
||||
List(Square.F5, Square.E5, Square.D6, Square.E6, Square.F6, Square.D7, Square.E7, Square.F7)
|
||||
game
|
||||
@@ -44,12 +44,12 @@ class AtomicVariantTest extends ChessTest:
|
||||
|
||||
test("Not allow a king to capture a piece"):
|
||||
val fenPosition = FullFen("8/8/8/1k6/8/8/8/1Kr5 w - -")
|
||||
val game = fenToGame(fenPosition, Atomic)
|
||||
val game = fenToGame(fenPosition, Atomic)
|
||||
assertEquals(game.playMoves((Square.B1, Square.C1)), Left(ErrorStr("Piece on b1 cannot move to c1")))
|
||||
|
||||
test("The game must end with the correct winner when a king explodes in the perimeter of a captured piece"):
|
||||
val fenPosition = FullFen("rnb1kbnr/ppp1pppp/8/3q4/8/7P/PPPP1PP1/RNBQKBNR b KQkq -")
|
||||
val game = fenToGame(fenPosition, Atomic)
|
||||
val game = fenToGame(fenPosition, Atomic)
|
||||
game
|
||||
.playMoves((Square.D5, Square.D2))
|
||||
.assertRight: g =>
|
||||
@@ -59,7 +59,7 @@ class AtomicVariantTest extends ChessTest:
|
||||
|
||||
test("The game must end by a traditional checkmate (atomic mate)"):
|
||||
val fenPosition = FullFen("1k6/8/8/8/8/8/PP5r/K7 b - -")
|
||||
val game = fenToGame(fenPosition, Atomic)
|
||||
val game = fenToGame(fenPosition, Atomic)
|
||||
game
|
||||
.playMoves((Square.H2, Square.H1))
|
||||
.assertRight: g =>
|
||||
@@ -69,7 +69,7 @@ class AtomicVariantTest extends ChessTest:
|
||||
|
||||
test("Must be a stalemate if a king could usually take a piece, but can't because it would explode"):
|
||||
val positionFen = FullFen("k7/8/1R6/8/8/8/8/5K2 w - -")
|
||||
val game = fenToGame(positionFen, Atomic)
|
||||
val game = fenToGame(positionFen, Atomic)
|
||||
game
|
||||
.playMoves((Square.B6, Square.B7))
|
||||
.assertRight: g =>
|
||||
@@ -78,7 +78,7 @@ class AtomicVariantTest extends ChessTest:
|
||||
|
||||
test("It is stalemate if there are only two kings and two opposite square coloured bishops remaining"):
|
||||
val positionFen = FullFen("4K3/8/2b5/8/8/8/5B2/3k4 b - -")
|
||||
val game = fenToGame(positionFen, Atomic)
|
||||
val game = fenToGame(positionFen, Atomic)
|
||||
assert(game.position.end)
|
||||
assert(game.position.autoDraw)
|
||||
assertEquals(game.position.winner, None)
|
||||
@@ -88,7 +88,7 @@ class AtomicVariantTest extends ChessTest:
|
||||
"In atomic check, an opportunity at exploding the opponent's king takes priority over getting out of check"
|
||||
):
|
||||
val positionFen = FullFen("k1K5/pp5R/8/8/3Q4/P7/1P6/2r5 w - -")
|
||||
val game = fenToGame(positionFen, Atomic)
|
||||
val game = fenToGame(positionFen, Atomic)
|
||||
assert(game.position.check.yes)
|
||||
assertNot(game.position.end)
|
||||
assertEquals(game.position.winner, None)
|
||||
@@ -120,7 +120,7 @@ class AtomicVariantTest extends ChessTest:
|
||||
"In atomic mate, an opportunity at exploding the opponent's king takes priority over getting out of mate"
|
||||
):
|
||||
val positionFen = FullFen("k1r5/pp5R/8/8/3Q4/8/PP6/K7 b - -")
|
||||
val game = fenToGame(positionFen, Atomic)
|
||||
val game = fenToGame(positionFen, Atomic)
|
||||
game
|
||||
.playMoves((Square.C8, Square.C1))
|
||||
.assertRight: game =>
|
||||
@@ -133,7 +133,7 @@ class AtomicVariantTest extends ChessTest:
|
||||
"In atomic chess a king may walk into a square that is in the perimeter of the opponent king since it can't capture"
|
||||
):
|
||||
val positionFen = FullFen("3k4/8/3K4/8/8/8/7r/8 w - -")
|
||||
val game = fenToGame(positionFen, Atomic)
|
||||
val game = fenToGame(positionFen, Atomic)
|
||||
game
|
||||
.playMoves((Square.D6, Square.D7))
|
||||
.assertRight: game =>
|
||||
@@ -142,7 +142,7 @@ class AtomicVariantTest extends ChessTest:
|
||||
|
||||
test("Draw on knight and king vs king"):
|
||||
val position = FullFen("8/1n6/8/8/8/8/k7/2K1b2R w - -")
|
||||
val game = fenToGame(position, Atomic)
|
||||
val game = fenToGame(position, Atomic)
|
||||
game
|
||||
.playMoves((Square.H1, Square.E1))
|
||||
.assertRight: game =>
|
||||
@@ -151,7 +151,7 @@ class AtomicVariantTest extends ChessTest:
|
||||
|
||||
test("Draw on bishop and king vs king"):
|
||||
val position = FullFen("8/1b6/8/8/8/8/k7/2K1n2R w - -")
|
||||
val game = fenToGame(position, Atomic)
|
||||
val game = fenToGame(position, Atomic)
|
||||
game
|
||||
.playMoves((Square.H1, Square.E1))
|
||||
.assertRight: game =>
|
||||
@@ -160,7 +160,7 @@ class AtomicVariantTest extends ChessTest:
|
||||
|
||||
test("Draw on a rook and king vs king"):
|
||||
val position = FullFen("8/8/8/8/8/8/N4r2/5k1K b - -")
|
||||
val game = fenToGame(position, Atomic)
|
||||
val game = fenToGame(position, Atomic)
|
||||
game
|
||||
.playMoves((Square.F2, Square.A2))
|
||||
.assertRight: game =>
|
||||
@@ -169,7 +169,7 @@ class AtomicVariantTest extends ChessTest:
|
||||
|
||||
test("Draw on a king vs a king"):
|
||||
val position = FullFen("6r1/8/8/1k6/8/8/2K5/6R1 w - -")
|
||||
val game = fenToGame(position, Atomic)
|
||||
val game = fenToGame(position, Atomic)
|
||||
game
|
||||
.playMoves((Square.G1, Square.G8))
|
||||
.assertRight: game =>
|
||||
@@ -178,14 +178,14 @@ class AtomicVariantTest extends ChessTest:
|
||||
|
||||
test("It should not be possible to capture a piece resulting in your own king exploding"):
|
||||
val position = FullFen("rnbqkbnr/pppNp1pp/5p2/3p4/8/8/PPPPPPPP/RNBQKB1R b KQkq - 1 3")
|
||||
val game = fenToGame(position, Atomic)
|
||||
val game = fenToGame(position, Atomic)
|
||||
assertEquals(game.playMoves((Square.D8, Square.D7)), Left(ErrorStr("Piece on d8 cannot move to d7")))
|
||||
|
||||
test(
|
||||
"In an en-passant capture, the pieces surrounding the pawn's destination are exploded along with the pawn"
|
||||
):
|
||||
val position = FullFen("4k3/2pppb1p/3r1r2/3P1b2/8/8/1K6/4NB2 b - -")
|
||||
val game = fenToGame(position, Atomic)
|
||||
val game = fenToGame(position, Atomic)
|
||||
game
|
||||
.playMoves((Square.E7, Square.E5), (Square.D5, Square.E6))
|
||||
.assertRight: game =>
|
||||
@@ -197,17 +197,17 @@ class AtomicVariantTest extends ChessTest:
|
||||
|
||||
test("Verify it is not possible to walk into check"):
|
||||
val position = FullFen("rnbqkbnr/ppp1pppp/8/3pN3/8/8/PPPPPPPP/RNBQKB1R b KQkq - 1 2")
|
||||
val game = fenToGame(position, Atomic)
|
||||
val game = fenToGame(position, Atomic)
|
||||
assertEquals(game.playMoves((Square.E8, Square.D7)), Left(ErrorStr("Piece on e8 cannot move to d7")))
|
||||
|
||||
test("Verify that a king can move into what would traditionally be check when touching the opponent king"):
|
||||
val position = FullFen("r1bq1bnr/pppp1ppp/5k2/4p3/4P1K1/8/PPPP1PPP/RNBQ1B1R b - - 5 6")
|
||||
val game = fenToGame(position, Atomic)
|
||||
val game = fenToGame(position, Atomic)
|
||||
assert(game.playMoves((Square.F6, Square.F5)).isRight)
|
||||
|
||||
test("After kings have been touching, and one moves away, a king that was protected is under attack again"):
|
||||
val position = FullFen("r1bq1bnr/pppp1ppp/5k2/4p3/4P1K1/8/PPPP1PPP/RNBQ1B1R b - - 5 6")
|
||||
val game = fenToGame(position, Atomic)
|
||||
val game = fenToGame(position, Atomic)
|
||||
game
|
||||
.playMoves((Square.F6, Square.F5), (Square.G4, Square.H3))
|
||||
.assertRight: game =>
|
||||
@@ -215,7 +215,7 @@ class AtomicVariantTest extends ChessTest:
|
||||
|
||||
test("Can move into discovered check in order to explode the opponent's king"):
|
||||
val position = FullFen("R2r2k1/1p2ppbp/8/6p1/2p5/5P1N/P2Pn1PP/2B1K2R b K - 3 19")
|
||||
val game = fenToGame(position, Atomic)
|
||||
val game = fenToGame(position, Atomic)
|
||||
game
|
||||
.playMoves((Square.D8, Square.D2))
|
||||
.assertRight: game =>
|
||||
@@ -226,7 +226,7 @@ class AtomicVariantTest extends ChessTest:
|
||||
"It must be possible to remove yourself from check by exploding a piece next to the piece threatening the king"
|
||||
):
|
||||
val position = FullFen("5k1r/p1ppq1pp/5p2/1B6/1b3P2/2P5/PP4PP/RNB1K2R w KQ - 0 12")
|
||||
val game = fenToGame(position, Atomic)
|
||||
val game = fenToGame(position, Atomic)
|
||||
game
|
||||
.playMoves((Square.B5, Square.D7))
|
||||
.assertRight: game =>
|
||||
@@ -236,14 +236,14 @@ class AtomicVariantTest extends ChessTest:
|
||||
"It should not be possible to explode a piece, exploding a piece next to it which would result in a check"
|
||||
):
|
||||
val position = FullFen("r1b1k2r/pp1pBppp/2p1p2n/q3P3/B2P4/2N2Q2/PPn2PPP/R3K1NR w KQkq - 9 11")
|
||||
val game = fenToGame(position, Atomic)
|
||||
val game = fenToGame(position, Atomic)
|
||||
assertEquals(game.playMoves((Square.A4, Square.C2)), Left(ErrorStr("Piece on a4 cannot move to c2")))
|
||||
|
||||
test(
|
||||
"Game is not a draw when the last piece a player has other than their king is a pawn that is blocked by a mobile piece"
|
||||
):
|
||||
val position = FullFen("3Q4/2b2k2/5P2/8/8/8/6K1/8 b - - 0 57")
|
||||
val game = fenToGame(position, Atomic)
|
||||
val game = fenToGame(position, Atomic)
|
||||
game
|
||||
.playMoves((Square.C7, Square.D8))
|
||||
.assertRight: game =>
|
||||
@@ -254,7 +254,7 @@ class AtomicVariantTest extends ChessTest:
|
||||
test("There are no repeated moves in the list of available moves for the board"):
|
||||
// Board where the queen can capture a pawn to both win and remove itself from check
|
||||
val position = FullFen("k1r5/pp5Q/8/8/8/8/PP6/2K5 w - -")
|
||||
val game = fenToGame(position, Atomic)
|
||||
val game = fenToGame(position, Atomic)
|
||||
game.position.moves
|
||||
.get(Square.H7)
|
||||
.assertSome: queenMoves =>
|
||||
@@ -287,7 +287,7 @@ class AtomicVariantTest extends ChessTest:
|
||||
|
||||
test("Identify that a player does not have sufficient material to win when they only have a king"):
|
||||
val position = FullFen("8/8/8/8/7p/2k4q/2K3P1/8 w - - 19 54")
|
||||
val game = fenToGame(position, Atomic)
|
||||
val game = fenToGame(position, Atomic)
|
||||
assertNot(game.position.end)
|
||||
game
|
||||
.playMoves(Square.G2 -> Square.H3)
|
||||
|
||||
@@ -378,7 +378,7 @@ K bB""".autoDraw
|
||||
|
||||
test("do not detect insufficient material: on two knights"):
|
||||
val position = FullFen("1n2k1n1/8/8/8/8/8/8/4K3 w - - 0 1")
|
||||
val game = fenToGame(position, Standard)
|
||||
val game = fenToGame(position, Standard)
|
||||
assertNot(game.position.autoDraw)
|
||||
assertNot(game.position.end)
|
||||
assertNot(game.position.opponentHasInsufficientMaterial)
|
||||
@@ -440,7 +440,7 @@ K bB""".autoDraw
|
||||
assertNot(game.position.opponentHasInsufficientMaterial)
|
||||
test("do not detect insufficient material: on same-color bishops on both sides"):
|
||||
val position = FullFen("5K2/8/8/1B6/8/k7/6b1/8 w - - 0 39")
|
||||
val game = fenToGame(position, Standard)
|
||||
val game = fenToGame(position, Standard)
|
||||
assert(game.position.autoDraw)
|
||||
assert(game.position.end)
|
||||
assert(game.position.opponentHasInsufficientMaterial)
|
||||
|
||||
@@ -13,8 +13,8 @@ import format.Uci
|
||||
class CanPlayTest extends MunitExtensions with SnapshotAssertions:
|
||||
|
||||
test("playPositions from standard prod games"):
|
||||
val nb = 10
|
||||
val games = Fixtures.prod500standard
|
||||
val nb = 10
|
||||
val games = Fixtures.prod500standard
|
||||
val result = games
|
||||
.take(nb)
|
||||
.map(g => SanStr.from(g.split(' ').toList))
|
||||
@@ -27,20 +27,20 @@ class CanPlayTest extends MunitExtensions with SnapshotAssertions:
|
||||
assertFileSnapshot(result.writeFen, "canplay/playPositions_racing_kings.txt")
|
||||
|
||||
test("validate from prod games"):
|
||||
val games = Fixtures.prod500standard
|
||||
val games = Fixtures.prod500standard
|
||||
val result = games
|
||||
.map(g => SanStr.from(g.split(' ').toList))
|
||||
.traverse_(moves => Standard.initialPosition.validate(moves))
|
||||
assertEquals(result, ().asRight)
|
||||
|
||||
test("validate return left on invalid move"):
|
||||
val moves = List(uci"e2e4", uci"e7e5", uci"g1f3", uci"b8b6")
|
||||
val moves = List(uci"e2e4", uci"e7e5", uci"g1f3", uci"b8b6")
|
||||
val result = Standard.initialPosition.validate(moves)
|
||||
assert(result.isLeft)
|
||||
|
||||
test("forwad from prod games"):
|
||||
val nb = 10
|
||||
val games = Fixtures.prod500standard
|
||||
val nb = 10
|
||||
val games = Fixtures.prod500standard
|
||||
val result = games
|
||||
.take(nb)
|
||||
.map(g => SanStr.from(g.split(' ').toList))
|
||||
@@ -49,16 +49,16 @@ class CanPlayTest extends MunitExtensions with SnapshotAssertions:
|
||||
|
||||
test("playWhileValid and playWhileValidReverse from prod games"):
|
||||
val sans = SanStr.from(Fixtures.fromProd2.split(' ').toList)
|
||||
val x = Standard.initialPosition.playWhileValid(sans, Ply.initial)(_.move.toUci).toOption.get
|
||||
val y = Standard.initialPosition.playWhileValidReverse(sans, Ply.initial)(_.move.toUci).toOption.get
|
||||
val x = Standard.initialPosition.playWhileValid(sans, Ply.initial)(_.move.toUci).toOption.get
|
||||
val y = Standard.initialPosition.playWhileValidReverse(sans, Ply.initial)(_.move.toUci).toOption.get
|
||||
assertEquals(x.moves, y.moves.reverse)
|
||||
assertEquals(x.error, y.error)
|
||||
assertEquals(x.state.board, y.state.board)
|
||||
|
||||
test("foldLeft and foldRight has correct plies"):
|
||||
val sans = SanStr.from(Fixtures.fromProd2.split(' ').toList)
|
||||
val x = Standard.initialPosition.foldLeft(sans, Ply.initial)(List.empty, (xs, step) => step.ply :: xs)
|
||||
val y = Standard.initialPosition.foldRight(sans, Ply.initial)(List.empty, (step, xs) => step.ply :: xs)
|
||||
val x = Standard.initialPosition.foldLeft(sans, Ply.initial)(List.empty, (xs, step) => step.ply :: xs)
|
||||
val y = Standard.initialPosition.foldRight(sans, Ply.initial)(List.empty, (step, xs) => step.ply :: xs)
|
||||
assertEquals(y.result(0), Ply.initial.next)
|
||||
assertEquals(x.result, y.result.reverse)
|
||||
assertEquals(x.error, None)
|
||||
@@ -66,10 +66,10 @@ class CanPlayTest extends MunitExtensions with SnapshotAssertions:
|
||||
|
||||
test("foldLeft from foreach"):
|
||||
val sans = SanStr.from(Fixtures.fromProd2.split(' ').toList)
|
||||
val x =
|
||||
val x =
|
||||
Standard.initialPosition.foldLeft(sans, Ply.initial)(List.empty, (xs, step) => xs :+ step.move.toUci)
|
||||
val builder = scala.collection.mutable.ListBuffer.empty[Uci]
|
||||
val y = Standard.initialPosition.foreach(sans, Ply.initial)(step => builder += step.move.toUci)
|
||||
val y = Standard.initialPosition.foreach(sans, Ply.initial)(step => builder += step.move.toUci)
|
||||
assertEquals(x.result, builder.toList)
|
||||
assertEquals(x.error, None)
|
||||
assertEquals(y, None)
|
||||
@@ -77,12 +77,12 @@ class CanPlayTest extends MunitExtensions with SnapshotAssertions:
|
||||
/*============== Error Messages ==============*/
|
||||
|
||||
test("error message for white"):
|
||||
val sans = List(SanStr("Nf7"))
|
||||
val sans = List(SanStr("Nf7"))
|
||||
val error = Standard.initialPosition.playPositions(sans).swap.toOption.get
|
||||
assertEquals(error, ErrorStr("Cannot play Nf7 at move 1 by white"))
|
||||
|
||||
test("error message for black"):
|
||||
val sans = List("e4", "e4").map(SanStr(_))
|
||||
val sans = List("e4", "e4").map(SanStr(_))
|
||||
val error = Standard.initialPosition.playPositions(sans).swap.toOption.get
|
||||
assertEquals(error, ErrorStr("Cannot play e4 at move 1 by black"))
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ class CastlingKingSideTest extends ChessTest:
|
||||
val goodHist = """
|
||||
PPPPPPPP
|
||||
R QK R"""
|
||||
val badHist = goodHist.updateHistory(_.withoutCastles(White))
|
||||
val badHist = goodHist.updateHistory(_.withoutCastles(White))
|
||||
test("impossible"):
|
||||
assertEquals(goodHist.place(White.bishop, F1).flatMap(_.destsFrom(E1)), Set())
|
||||
assertEquals(goodHist.place(White.knight, G1).flatMap(_.destsFrom(E1)), Set(F1))
|
||||
|
||||
@@ -13,7 +13,7 @@ class CastlingQueenSideTest extends ChessTest:
|
||||
val goodHist = """
|
||||
PPPPPPPP
|
||||
R KB R"""
|
||||
val badHist = goodHist.updateHistory(_.withoutCastles(White))
|
||||
val badHist = goodHist.updateHistory(_.withoutCastles(White))
|
||||
test("impossible: near queen in the way"):
|
||||
assertEquals(goodHist.place(White.queen, D1).flatMap(_.destsFrom(E1)), Set())
|
||||
test("impossible: bishop in the way"):
|
||||
@@ -39,7 +39,7 @@ PPPPPPPP
|
||||
R K R""".updateHistory(_ => castleHistory(White, kingSide = true, queenSide = true))
|
||||
test("impact history: if king castles kingside"):
|
||||
val game = Game(board)
|
||||
val g2 = game.playMove(E1, G1).get
|
||||
val g2 = game.playMove(E1, G1).get
|
||||
assertGame(
|
||||
g2,
|
||||
"""
|
||||
@@ -58,7 +58,7 @@ R RK """
|
||||
)
|
||||
test("impact history: if king castles queenside"):
|
||||
val game = Game(board)
|
||||
val g2 = game.playMove(E1, C1).get
|
||||
val g2 = game.playMove(E1, C1).get
|
||||
assertGame(
|
||||
g2,
|
||||
"""
|
||||
@@ -77,37 +77,37 @@ PPPPPPPP
|
||||
)
|
||||
test("impact history: if king moves to the right"):
|
||||
val game = Game(board)
|
||||
val g2 = game.playMove(E1, F1).get.as(White)
|
||||
val g2 = game.playMove(E1, F1).get.as(White)
|
||||
assertEquals(g2.position.destsFrom(F1), Set(E1, G1))
|
||||
val g3 = g2.playMove(F1, E1).get.as(White)
|
||||
assertEquals(g3.position.destsFrom(E1), Set(D1, F1))
|
||||
test("impact history: if king moves to the left"):
|
||||
val game = Game(board)
|
||||
val g2 = game.playMove(E1, D1).get.as(White)
|
||||
val g2 = game.playMove(E1, D1).get.as(White)
|
||||
assertEquals(g2.position.destsFrom(D1), Set(C1, E1))
|
||||
val g3 = g2.playMove(D1, E1).get.as(White)
|
||||
assertEquals(g3.position.destsFrom(E1), Set(D1, F1))
|
||||
test("impact history: if kingside rook moves"):
|
||||
val game = Game(board)
|
||||
val g2 = game.playMove(H1, G1).get.as(White)
|
||||
val g2 = game.playMove(H1, G1).get.as(White)
|
||||
assertEquals(g2.position.destsFrom(E1), Set(C1, D1, F1, A1))
|
||||
val g3 = g2.playMove(A1, B1).get
|
||||
assertEquals(g3.position.destsFrom(E1), Set(D1, F1))
|
||||
test("impact history: if queenside rook moves"):
|
||||
val game = Game(board)
|
||||
val g2 = game.playMove(A1, B1).get.as(White)
|
||||
val g2 = game.playMove(A1, B1).get.as(White)
|
||||
assertEquals(g2.position.destsFrom(E1), Set(D1, F1, G1, H1))
|
||||
val g3 = g2.playMove(H1, G1).get
|
||||
assertEquals(g3.position.destsFrom(E1), Set(D1, F1))
|
||||
|
||||
test("chess960"):
|
||||
val fenPosition = FullFen("r3k2r/8/8/8/8/8/8/1R2K2R b KQk - 1 1")
|
||||
val init = fenToGame(fenPosition, Chess960)
|
||||
val game = init.playMoves((A8, A1), (H1, H2), (A1, A7)).get
|
||||
val init = fenToGame(fenPosition, Chess960)
|
||||
val game = init.playMoves((A8, A1), (H1, H2), (A1, A7)).get
|
||||
assert(game.position.legalMoves.exists(_.castles))
|
||||
|
||||
test("test games"):
|
||||
val fenPosition = FullFen("1rnk1bqr/pppp1bpp/1n2p3/5p2/5P2/1N1N4/PPPPPBPP/1R1K1BQR w KQkq - 0 5")
|
||||
val init = fenToGame(fenPosition, Chess960)
|
||||
val init = fenToGame(fenPosition, Chess960)
|
||||
assert(init.position.legalMoves.exists(_.castles))
|
||||
assert(init.position.legalMoves.exists(_.castle.exists(_.side == QueenSide)))
|
||||
|
||||
@@ -11,17 +11,17 @@ import variant.{ Chess960, Variant, Standard, Crazyhouse }
|
||||
trait ChessTestCommon:
|
||||
|
||||
given Conversion[String, Position] = Visual.<<
|
||||
given Conversion[String, PgnStr] = PgnStr(_)
|
||||
given Conversion[PgnStr, String] = _.value
|
||||
given Conversion[String, PgnStr] = PgnStr(_)
|
||||
given Conversion[PgnStr, String] = _.value
|
||||
|
||||
extension (str: String)
|
||||
def chess960: Position = makeBoard(str, chess.variant.Chess960)
|
||||
def kingOfTheHill: Position = makeBoard(str, chess.variant.KingOfTheHill)
|
||||
def threeCheck: Position = makeBoard(str, chess.variant.ThreeCheck)
|
||||
def chess960: Position = makeBoard(str, chess.variant.Chess960)
|
||||
def kingOfTheHill: Position = makeBoard(str, chess.variant.KingOfTheHill)
|
||||
def threeCheck: Position = makeBoard(str, chess.variant.ThreeCheck)
|
||||
def as(color: Color): Position = (Visual << str).withColor(color)
|
||||
|
||||
extension (board: Position)
|
||||
def visual = Visual >> board
|
||||
def visual = Visual >> board
|
||||
def destsFrom(from: Square): Option[List[Square]] =
|
||||
board
|
||||
.pieceAt(from)
|
||||
@@ -93,7 +93,7 @@ trait ChessTestCommon:
|
||||
def makeChess960Board(position: Int) =
|
||||
Position(Board.fromMap(Chess960.initialPieces(position)), Chess960, White)
|
||||
def makeChess960Game(position: Int) = Game(makeChess960Board(position))
|
||||
def chess960Boards = (0 to 959).map(makeChess960Board).toList
|
||||
def chess960Boards = (0 to 959).map(makeChess960Board).toList
|
||||
|
||||
def makeEmptyBoard: Position = Position(Board.empty, Standard, White)
|
||||
|
||||
@@ -150,8 +150,8 @@ trait MunitExtensions extends munit.FunSuite:
|
||||
given [A, B](using comp: munit.Compare[A, B]): munit.Compare[Option[A], Option[B]] with
|
||||
def isEqual(obtained: Option[A], expected: Option[B]): Boolean = (obtained, expected) match
|
||||
case (Some(o), Some(e)) => comp.isEqual(o, e)
|
||||
case (None, None) => true
|
||||
case _ => false
|
||||
case (None, None) => true
|
||||
case _ => false
|
||||
|
||||
given [A, B](using sr: SameRuntime[B, A]): munit.Compare[List[A], List[B]] with
|
||||
def isEqual(obtained: List[A], expected: List[B]): Boolean =
|
||||
@@ -164,21 +164,21 @@ trait MunitExtensions extends munit.FunSuite:
|
||||
extension [A](v: Option[A])
|
||||
def assertSome(f: PartialFunction[A, Unit])(using Location): Any = v match
|
||||
case Some(a) => f.lift(a).getOrElse(fail(s"Unexpected Some value: $a"))
|
||||
case None => fail(s"Expected Some but received None")
|
||||
case None => fail(s"Expected Some but received None")
|
||||
|
||||
extension [E, A](v: Either[E, A])
|
||||
def assertRight(f: PartialFunction[A, Unit])(using Location): Any = v match
|
||||
case Right(r) => f.lift(r).getOrElse(fail(s"Unexpected Right value: $r"))
|
||||
case Left(_) => fail(s"Expected Right but received $v")
|
||||
case Left(_) => fail(s"Expected Right but received $v")
|
||||
def get: A = v match
|
||||
case Right(r) => r
|
||||
case Left(_) => fail(s"Expected Right but received $v")
|
||||
case Left(_) => fail(s"Expected Right but received $v")
|
||||
|
||||
trait ChessTest extends munit.FunSuite with ChessTestCommon with MunitExtensions:
|
||||
import munit.Location
|
||||
|
||||
object clockConv:
|
||||
given Conversion[Int, Clock.LimitSeconds] = Clock.LimitSeconds(_)
|
||||
given Conversion[Int, Clock.LimitSeconds] = Clock.LimitSeconds(_)
|
||||
given Conversion[Int, Clock.IncrementSeconds] = Clock.IncrementSeconds(_)
|
||||
|
||||
object compare:
|
||||
|
||||
@@ -9,7 +9,7 @@ class ClockTest extends ChessTest:
|
||||
import clockConv.given
|
||||
|
||||
val clock = Clock(5 * 60 * 1000, 0)
|
||||
val game = makeGame.withClock(clock.start)
|
||||
val game = makeGame.withClock(clock.start)
|
||||
|
||||
test("play with a clock: new game"):
|
||||
assertEquals(game.clock.map { _.color }, Option(White))
|
||||
@@ -58,7 +58,7 @@ class ClockLagCompTest extends ChessTest:
|
||||
.remainingTime(Black))
|
||||
.centis
|
||||
|
||||
def clockStep60(w: Int, l: Int*) = clockStep(fakeClock60, w, l*)
|
||||
def clockStep60(w: Int, l: Int*) = clockStep(fakeClock60, w, l*)
|
||||
def clockStep600(w: Int, l: Int*) = clockStep(fakeClock600, w, l*)
|
||||
|
||||
def clockStart(lag: Int) =
|
||||
|
||||
@@ -47,7 +47,7 @@ class CrazyhouseDataTest extends ScalaCheckSuite:
|
||||
property("store and drop multiple pieces with promotion"):
|
||||
forAll: (ps: List[Piece], square: Square, promoted: Bitboard) =>
|
||||
val filtered = ps.filter(_.role != King)
|
||||
val data = filtered.foldLeft(Data.init.copy(promoted = promoted)) { (data, piece) =>
|
||||
val data = filtered.foldLeft(Data.init.copy(promoted = promoted)) { (data, piece) =>
|
||||
data.store(piece, square)
|
||||
}
|
||||
assertEquals(data.size, filtered.size)
|
||||
|
||||
@@ -10,19 +10,19 @@ class CrazyhouseVariantTest extends ChessTest:
|
||||
|
||||
test("nothing to drop"):
|
||||
val fenPosition = FullFen("3Nkb1r/1pQP1ppp/4p3/3N4/N5N1/6B1/PPPPBPPP/R1B2RK1 b - - 0 25")
|
||||
val game = fenToGame(fenPosition, Crazyhouse).updatePosition: b =>
|
||||
val game = fenToGame(fenPosition, Crazyhouse).updatePosition: b =>
|
||||
b.withCrazyData(Crazyhouse.Data.init)
|
||||
assert(game.position.checkMate)
|
||||
assertNot(game.position.opponentHasInsufficientMaterial)
|
||||
|
||||
test("checkmate"):
|
||||
val fenPosition = FullFen("r2q1b1r/ppp1kPpp/2p5/2PpN3/1n1Pb3/3PK3/PPr1BPPP/n1q1N2R/b w - - 0 20")
|
||||
val game = fenToGame(fenPosition, Crazyhouse)
|
||||
val game = fenToGame(fenPosition, Crazyhouse)
|
||||
assert(game.position.checkMate)
|
||||
|
||||
test("pieces to drop, in vain"):
|
||||
val fenPosition = FullFen("3Nkb1r/1pQP1ppp/4p3/3N4/N5N1/6B1/PPPPBPPP/R1B2RK1 b - - 0 25")
|
||||
val game = fenToGame(fenPosition, Crazyhouse).updatePosition: b =>
|
||||
val game = fenToGame(fenPosition, Crazyhouse).updatePosition: b =>
|
||||
b.withCrazyData(
|
||||
Crazyhouse.Data(
|
||||
pockets = ByColor(
|
||||
@@ -260,7 +260,7 @@ class CrazyhouseVariantTest extends ChessTest:
|
||||
|
||||
test("autodraw: not draw when only kings left"):
|
||||
val fenPosition = FullFen("k6K/8/8/8/8/8/8/8 w - - 0 25")
|
||||
val game = fenToGame(fenPosition, Crazyhouse)
|
||||
val game = fenToGame(fenPosition, Crazyhouse)
|
||||
assertNot(game.position.autoDraw)
|
||||
assertNot(game.position.end)
|
||||
assertNot(game.position.opponentHasInsufficientMaterial)
|
||||
@@ -343,7 +343,7 @@ class CrazyhouseVariantTest extends ChessTest:
|
||||
|
||||
test("Index out of bounds when hashing pockets"):
|
||||
val fenPosition = FullFen("2q1k1nr/B3bbrb/8/8/8/8/3qN1RB/1Q2KB1R/RRRQQQQQQrrrqqq w Kk - 0 11")
|
||||
val game = fenToGame(fenPosition, Crazyhouse)
|
||||
val game = fenToGame(fenPosition, Crazyhouse)
|
||||
assert(game.apply(E1, D2).isRight)
|
||||
|
||||
case class DropTestCase(fen: FullFen, drops: Option[Set[Square]])
|
||||
|
||||
@@ -17,9 +17,9 @@ class DecayingStatsTest extends ChessTest:
|
||||
.toFloat / (elts.size - 1)
|
||||
|
||||
val randoms: Array[Float] = Array.fill(1000) { random.nextGaussian.toFloat }
|
||||
val data10: Array[Float] = randoms.map { _ + 10 }
|
||||
val data10: Array[Float] = randoms.map { _ + 10 }
|
||||
|
||||
val stats10 = DS(10, 100, .9f).record(data10)
|
||||
val stats10 = DS(10, 100, .9f).record(data10)
|
||||
val stats10d = DS(10, 100, 0.99f).record(data10)
|
||||
|
||||
test("gaussian data: eventually converge with constant mean"):
|
||||
@@ -30,7 +30,7 @@ class DecayingStatsTest extends ChessTest:
|
||||
assertCloseTo(stats10d.mean, 10f, 0.1f)
|
||||
|
||||
test("gaussian data: eventually converge with second mean"):
|
||||
val stats2 = stats10.record(randoms)
|
||||
val stats2 = stats10.record(randoms)
|
||||
val stats2d = stats10d.record(randoms)
|
||||
|
||||
assertCloseTo(stats2.deviation, 1f, 0.25f)
|
||||
@@ -45,7 +45,7 @@ class DecayingStatsTest extends ChessTest:
|
||||
assertCloseTo(stats10d.record(randoms.take(100)).mean, 0f, 4f)
|
||||
|
||||
test("gaussian data: converge with interleave"):
|
||||
val dataI = Array(data10, randoms).flatMap(_.zipWithIndex).sortBy(_._2).map(_._1)
|
||||
val dataI = Array(data10, randoms).flatMap(_.zipWithIndex).sortBy(_._2).map(_._1)
|
||||
val statsIa = DS(10, 100, .9f).record(dataI)
|
||||
val statsIb = DS(10, 100, 0.99f).record(dataI)
|
||||
|
||||
@@ -56,7 +56,7 @@ class DecayingStatsTest extends ChessTest:
|
||||
assertCloseTo(statsIb.mean, 5f, 0.25f)
|
||||
|
||||
test("flip flop data should converge reasonably"):
|
||||
val data = Array.iterate(0f, 1000) { 1f - _ }
|
||||
val data = Array.iterate(0f, 1000) { 1f - _ }
|
||||
val stats = DS(0, 10, .9f).record(data)
|
||||
assertCloseTo(stats.mean, .5f, 0.05f)
|
||||
assertCloseTo(stats.deviation, .5f, 0.05f)
|
||||
|
||||
@@ -20,7 +20,7 @@ class HasIdTest extends ScalaCheckSuite:
|
||||
|
||||
test("removeById reserves order"):
|
||||
forAll: (xs: List[Int], id: Int) =>
|
||||
val sorted = xs.sorted
|
||||
val sorted = xs.sorted
|
||||
val removed = sorted.removeById(id)
|
||||
removed == removed.sorted
|
||||
|
||||
|
||||
@@ -8,51 +8,51 @@ import variant.{ Antichess, Atomic, Crazyhouse, Standard, ThreeCheck }
|
||||
class HashTest extends ChessTest:
|
||||
|
||||
test("Polyglot hasher: match on the starting position"):
|
||||
val fen = FullFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
|
||||
val fen = FullFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
|
||||
val game = fenToGame(fen, Standard)
|
||||
assertEquals(Hash(game.position), Hash(0x463b_9618))
|
||||
|
||||
test("Polyglot hasher: match after 1. e4"):
|
||||
val fen = FullFen("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1")
|
||||
val fen = FullFen("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1")
|
||||
val game = fenToGame(fen, Standard)
|
||||
assertEquals(Hash(game.position), Hash(0x823c_9b50))
|
||||
|
||||
test("Polyglot hasher: match after 1. e4 d5"):
|
||||
val fen = FullFen("rnbqkbnr/ppp1pppp/8/3p4/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2")
|
||||
val fen = FullFen("rnbqkbnr/ppp1pppp/8/3p4/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2")
|
||||
val game = fenToGame(fen, Standard)
|
||||
assertEquals(Hash(game.position), Hash(0x0756_b944))
|
||||
|
||||
test("Polyglot hasher: match after 1. e4 d5 2. e5"):
|
||||
val fen = FullFen("rnbqkbnr/ppp1pppp/8/3pP3/8/8/PPPP1PPP/RNBQKBNR b KQkq - 0 2")
|
||||
val fen = FullFen("rnbqkbnr/ppp1pppp/8/3pP3/8/8/PPPP1PPP/RNBQKBNR b KQkq - 0 2")
|
||||
val game = fenToGame(fen, Standard)
|
||||
assertEquals(Hash(game.position), Hash(0x662f_afb9))
|
||||
|
||||
test("Polyglot hasher: match after 1. e4 d5 2. e5 f5"):
|
||||
// note that en-passant matters
|
||||
val fen = FullFen("rnbqkbnr/ppp1p1pp/8/3pPp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3")
|
||||
val fen = FullFen("rnbqkbnr/ppp1p1pp/8/3pPp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3")
|
||||
val game = fenToGame(fen, Standard)
|
||||
assertEquals(Hash(game.position), Hash(0x22a4_8b5a))
|
||||
|
||||
test("Polyglot hasher: match after 1. e4 d5 2. e5 f5 3. Ke2"):
|
||||
// 3. Ke2 forfeits castling rights
|
||||
val fen = FullFen("rnbqkbnr/ppp1p1pp/8/3pPp2/8/8/PPPPKPPP/RNBQ1BNR b kq - 1 3")
|
||||
val fen = FullFen("rnbqkbnr/ppp1p1pp/8/3pPp2/8/8/PPPPKPPP/RNBQ1BNR b kq - 1 3")
|
||||
val game = fenToGame(fen, Standard)
|
||||
assertEquals(Hash(game.position), Hash(0x652a_607c))
|
||||
|
||||
test("Polyglot hasher: match after 1. e4 d5 2. e5 f5 3. Ke2 Kf7"):
|
||||
val fen = FullFen("rnbq1bnr/ppp1pkpp/8/3pPp2/8/8/PPPPKPPP/RNBQ1BNR w - - 2 4")
|
||||
val fen = FullFen("rnbq1bnr/ppp1pkpp/8/3pPp2/8/8/PPPPKPPP/RNBQ1BNR w - - 2 4")
|
||||
val game = fenToGame(fen, Standard)
|
||||
assertEquals(Hash(game.position), Hash(0x00fd_d303))
|
||||
|
||||
test("Polyglot hasher: match after 1. a4 b5 2. h4 b4 3. c4"):
|
||||
// again, note en-passant matters
|
||||
val fen = FullFen("rnbqkbnr/p1pppppp/8/8/PpP4P/8/1P1PPPP1/RNBQKBNR b KQkq c3 0 3")
|
||||
val fen = FullFen("rnbqkbnr/p1pppppp/8/8/PpP4P/8/1P1PPPP1/RNBQKBNR b KQkq c3 0 3")
|
||||
val game = fenToGame(fen, Standard)
|
||||
assertEquals(Hash(game.position), Hash(0x3c81_23ea))
|
||||
|
||||
test("Polyglot hasher: match after 1. a4 b5 2. h4 b4 3. c4 bxc3 4. Ra3"):
|
||||
// 4. Ra3 partially forfeits castling rights
|
||||
val fen = FullFen("rnbqkbnr/p1pppppp/8/8/P6P/R1p5/1P1PPPP1/1NBQKBNR b Kkq - 1 4")
|
||||
val fen = FullFen("rnbqkbnr/p1pppppp/8/8/P6P/R1p5/1P1PPPP1/1NBQKBNR b Kkq - 1 4")
|
||||
val game = fenToGame(fen, Standard)
|
||||
assertEquals(Hash(game.position), Hash(0x5c3f_9b82))
|
||||
|
||||
@@ -106,45 +106,45 @@ class HashTest extends ChessTest:
|
||||
|
||||
test("Hasher: be consistent in crazyhouse"):
|
||||
// from https://lichess.org/j4r7XHTB/black
|
||||
val fen = FullFen("r2qkb1r/ppp1pppp/2n2n2/3p2B1/3P2b1/4PN2/PPP1BPPP/RN1QK2R/ b KQkq - 9 5")
|
||||
val board = Fen.read(Crazyhouse, fen).get
|
||||
val move = board.move(Square.G4, Square.F3, None).get
|
||||
val fen = FullFen("r2qkb1r/ppp1pppp/2n2n2/3p2B1/3P2b1/4PN2/PPP1BPPP/RN1QK2R/ b KQkq - 9 5")
|
||||
val board = Fen.read(Crazyhouse, fen).get
|
||||
val move = board.move(Square.G4, Square.F3, None).get
|
||||
val hashAfterMove = Hash(move.after)
|
||||
|
||||
// 5 ... Bxf3
|
||||
val fenAfter = FullFen("r2qkb1r/ppp1pppp/2n2n2/3p2B1/3P4/4Pb2/PPP1BPPP/RN1QK2R/n w KQkq - 10 6")
|
||||
val fenAfter = FullFen("r2qkb1r/ppp1pppp/2n2n2/3p2B1/3P4/4Pb2/PPP1BPPP/RN1QK2R/n w KQkq - 10 6")
|
||||
val boardAfter = Fen.read(Crazyhouse, fenAfter).get
|
||||
val hashAfter = Hash(boardAfter)
|
||||
val hashAfter = Hash(boardAfter)
|
||||
|
||||
assertEquals(hashAfterMove, hashAfter)
|
||||
|
||||
test("Hasher: be consistent when king is captured in antichess"):
|
||||
val fen = FullFen("rnbqkb1r/ppp1pppp/3p1n2/1B6/8/4P3/PPPP1PPP/RNBQK1NR w KQkq - 2 3")
|
||||
val board = Fen.read(Antichess, fen).get
|
||||
val move = board.move(Square.B5, Square.E8, None).get
|
||||
val fen = FullFen("rnbqkb1r/ppp1pppp/3p1n2/1B6/8/4P3/PPPP1PPP/RNBQK1NR w KQkq - 2 3")
|
||||
val board = Fen.read(Antichess, fen).get
|
||||
val move = board.move(Square.B5, Square.E8, None).get
|
||||
val hashAfterMove = Hash(move.after)
|
||||
|
||||
// 3. BxK
|
||||
val fenAfter = FullFen("rnbqBb1r/ppp1pppp/3p1n2/8/8/4P3/PPPP1PPP/RNBQK1NR b KQkq - 0 3")
|
||||
val fenAfter = FullFen("rnbqBb1r/ppp1pppp/3p1n2/8/8/4P3/PPPP1PPP/RNBQK1NR b KQkq - 0 3")
|
||||
val boardAfter = Fen.read(Antichess, fenAfter).get
|
||||
val hashAfter = Hash(boardAfter)
|
||||
val hashAfter = Hash(boardAfter)
|
||||
|
||||
assertEquals(hashAfterMove, hashAfter)
|
||||
|
||||
test("Hasher: be consistent when rook is exploded in atomic"):
|
||||
val fen = FullFen("rnbqkb1r/ppppp1pp/5p1n/6N1/8/8/PPPPPPPP/RNBQKB1R w KQkq - 2 3")
|
||||
val board = Fen.read(Atomic, fen).get
|
||||
val move = board.move(Square.G5, Square.H7, None).get
|
||||
val fen = FullFen("rnbqkb1r/ppppp1pp/5p1n/6N1/8/8/PPPPPPPP/RNBQKB1R w KQkq - 2 3")
|
||||
val board = Fen.read(Atomic, fen).get
|
||||
val move = board.move(Square.G5, Square.H7, None).get
|
||||
val hashAfterMove = Hash(move.after)
|
||||
|
||||
// 3. Nxh7
|
||||
val fenAfter = FullFen("rnbqkb2/ppppp1p1/5p2/8/8/8/PPPPPPPP/RNBQKB1R b KQkq - 0 3")
|
||||
val fenAfter = FullFen("rnbqkb2/ppppp1p1/5p2/8/8/8/PPPPPPPP/RNBQKB1R b KQkq - 0 3")
|
||||
val boardAfter = Fen.read(Atomic, fenAfter).get
|
||||
val hashAfter = Hash(boardAfter)
|
||||
val hashAfter = Hash(boardAfter)
|
||||
|
||||
assertEquals(hashAfterMove, hashAfter)
|
||||
|
||||
test("Index out of bounds when hashing pockets"):
|
||||
val fenPosition = FullFen("2q1k1nr/B3bbrb/8/8/8/8/3qN1RB/1Q2KB1R/RRRQQQQQQrrrqqq w Kk - 0 11")
|
||||
val game = fenToGame(fenPosition, Crazyhouse)
|
||||
val game = fenToGame(fenPosition, Crazyhouse)
|
||||
assert(game.apply(E1, D2).isRight)
|
||||
|
||||
@@ -2,7 +2,7 @@ package chess
|
||||
|
||||
class ThreefoldRepetitionTest extends ChessTest:
|
||||
|
||||
def toHash(a: Int) = PositionHash(Hash(a << 8))
|
||||
def toHash(a: Int) = PositionHash(Hash(a << 8))
|
||||
def makeHistory(positions: List[Int]) =
|
||||
(positions
|
||||
.map(toHash))
|
||||
|
||||
@@ -15,7 +15,7 @@ object HordeInsufficientMaterialTest extends SimpleIOSuite:
|
||||
run("test-kit/src/test/resources/horde_insufficient_material.csv", Horde).map(expect(_))
|
||||
|
||||
given Monoid[Boolean] with
|
||||
def empty = true
|
||||
def empty = true
|
||||
def combine(x: Boolean, y: Boolean) = x && y
|
||||
|
||||
private def run(file: String, variant: Variant): IO[Boolean] =
|
||||
|
||||
@@ -9,84 +9,84 @@ class HordeVariantTest extends ChessTest:
|
||||
|
||||
test("Must not be insufficient winning material for horde with only 1 pawn left"):
|
||||
val position = FullFen("k7/ppP5/brp5/8/8/8/8/8 b - -")
|
||||
val game = fenToGame(position, Horde)
|
||||
val game = fenToGame(position, Horde)
|
||||
assertNot(game.position.opponentHasInsufficientMaterial)
|
||||
|
||||
test("Must recognise insufficient winning material for horde with only 1 pawn left"):
|
||||
val position = FullFen("8/2k5/3q4/8/8/8/1P6/8 b - -")
|
||||
val game = fenToGame(position, Horde)
|
||||
val game = fenToGame(position, Horde)
|
||||
assert(game.position.opponentHasInsufficientMaterial)
|
||||
|
||||
test("Must not be insufficient winning material for king with only 1 pawn left"):
|
||||
val position = FullFen("8/2k5/3q4/8/8/8/1P6/8 w - -")
|
||||
val game = fenToGame(position, Horde)
|
||||
val game = fenToGame(position, Horde)
|
||||
assertNot(game.position.opponentHasInsufficientMaterial)
|
||||
|
||||
test("Must recognise insufficient winning material for horde with only 1 bishop left"):
|
||||
val position = FullFen("r7/2Bb4/q3k3/8/8/3q4/8/5qqr b - -")
|
||||
val game = fenToGame(position, Horde)
|
||||
val game = fenToGame(position, Horde)
|
||||
assertNot(game.position.autoDraw)
|
||||
assertNot(game.position.end)
|
||||
assert(game.position.opponentHasInsufficientMaterial)
|
||||
|
||||
test("Must recognise insufficient winning material for horde with only 1 queen left"):
|
||||
val position = FullFen("8/2k5/3q4/8/8/1Q6/8/8 b - -")
|
||||
val game = fenToGame(position, Horde)
|
||||
val game = fenToGame(position, Horde)
|
||||
assert(game.position.opponentHasInsufficientMaterial)
|
||||
|
||||
test("Must not be insufficient winning material for king with only 1 queen left"):
|
||||
val position = FullFen("8/2k5/3q4/8/8/1Q6/8/8 w - -")
|
||||
val game = fenToGame(position, Horde)
|
||||
val game = fenToGame(position, Horde)
|
||||
assertNot(game.position.opponentHasInsufficientMaterial)
|
||||
|
||||
test("Must recognise insufficient winning material for horde with only 2 minor pieces left"):
|
||||
val position = FullFen("8/2k5/3q4/8/8/1B2N3/8/8 b - -")
|
||||
val game = fenToGame(position, Horde)
|
||||
val game = fenToGame(position, Horde)
|
||||
assert(game.position.opponentHasInsufficientMaterial)
|
||||
|
||||
test("Must not be insufficient winning material for king with only 2 minor pieces left"):
|
||||
val position = FullFen("8/2k5/3q4/8/8/1B2N3/8/8 w - -")
|
||||
val game = fenToGame(position, Horde)
|
||||
val game = fenToGame(position, Horde)
|
||||
assertNot(game.position.opponentHasInsufficientMaterial)
|
||||
|
||||
test("Must not be insufficient winning material for horde with 3 minor pieces left"):
|
||||
val position = FullFen("8/2k5/3q4/8/8/3B4/4NB2/8 b - -")
|
||||
val game = fenToGame(position, Horde)
|
||||
val game = fenToGame(position, Horde)
|
||||
assertNot(game.position.opponentHasInsufficientMaterial)
|
||||
|
||||
test("Must not be insufficient winning material for king with queen and rook left"):
|
||||
val position = FullFen("8/5k2/7q/7P/6rP/6P1/6P1/8 b - - 0 52")
|
||||
val game = fenToGame(position, Horde)
|
||||
val game = fenToGame(position, Horde)
|
||||
assertNot(game.position.opponentHasInsufficientMaterial)
|
||||
assertNot(game.position.autoDraw)
|
||||
|
||||
test("Must auto-draw in simple pawn fortress"):
|
||||
val position = FullFen("8/p7/pk6/P7/P7/8/8/8 b - -")
|
||||
val game = fenToGame(position, Horde)
|
||||
val game = fenToGame(position, Horde)
|
||||
assert(game.position.autoDraw)
|
||||
assert(game.position.opponentHasInsufficientMaterial)
|
||||
|
||||
test("Must auto-draw if horde is stalemated and only king can move"):
|
||||
val position = FullFen("QNBRRBNQ/PPpPPpPP/P1P2PkP/8/8/8/8/8 b - -")
|
||||
val game = fenToGame(position, Horde)
|
||||
val game = fenToGame(position, Horde)
|
||||
assert(game.position.autoDraw)
|
||||
assert(game.position.opponentHasInsufficientMaterial)
|
||||
|
||||
test("Must auto-draw if horde is stalemated and only king can move"):
|
||||
val position = FullFen("b7/pk6/P7/P7/8/8/8/8 b - - 0 1")
|
||||
val game = fenToGame(position, Horde)
|
||||
val game = fenToGame(position, Horde)
|
||||
assert(game.position.autoDraw)
|
||||
assert(game.position.opponentHasInsufficientMaterial)
|
||||
|
||||
test("Must not auto-draw if horde is not stalemated after the only king move"):
|
||||
val position = FullFen("8/1b5r/1P6/1Pk3q1/1PP5/r1P5/P1P5/2P5 b - - 0 52")
|
||||
val game = fenToGame(position, Horde)
|
||||
val game = fenToGame(position, Horde)
|
||||
assertNot(game.position.autoDraw)
|
||||
assertNot(game.position.opponentHasInsufficientMaterial)
|
||||
|
||||
test("Must not auto-draw if not all black King moves leads to stalemate"):
|
||||
val position = FullFen("8/8/8/7k/7P/7P/8/8 b - - 0 58")
|
||||
val game = fenToGame(position, Horde)
|
||||
val game = fenToGame(position, Horde)
|
||||
assertNot(game.position.autoDraw)
|
||||
assertNot(game.position.end)
|
||||
assertEquals(game.position.status, None)
|
||||
@@ -95,18 +95,18 @@ class HordeVariantTest extends ChessTest:
|
||||
|
||||
test("Must not auto-draw in B vs K endgame, king can win"):
|
||||
val position = FullFen("7B/6k1/8/8/8/8/8/8 b - -")
|
||||
val game = fenToGame(position, Horde)
|
||||
val game = fenToGame(position, Horde)
|
||||
assertNot(game.position.autoDraw)
|
||||
assert(game.position.opponentHasInsufficientMaterial)
|
||||
|
||||
test("Pawn on first rank should able to move two squares"):
|
||||
val position = FullFen("8/pp1k2q1/3P2p1/8/P3PP2/PPP2r2/PPP5/PPPP4 w - - 1 2")
|
||||
val game = fenToGame(position, Horde)
|
||||
val game = fenToGame(position, Horde)
|
||||
assert(game.position.legalMoves.exists(m => m.orig == Square.D1 && m.dest == Square.D3))
|
||||
|
||||
test("Cannot en passant a pawn from the first rank"):
|
||||
val position = FullFen("k7/5p2/4p2P/3p2P1/2p2P2/1p2P2P/p2P2P1/2P2P2 w - - 0 1")
|
||||
val game = fenToGame(position, Horde)(Square.C1, Square.C3).get
|
||||
val game = fenToGame(position, Horde)(Square.C1, Square.C3).get
|
||||
assertNot(game._1.position.legalMoves.exists(m => m.orig == Square.B3 && m.dest == Square.C2))
|
||||
|
||||
test("Castle with one rook moved"):
|
||||
@@ -125,6 +125,6 @@ class HordeVariantTest extends ChessTest:
|
||||
|
||||
test("the h8 rooks move"):
|
||||
val position = FullFen("r3kbnr/p1pqppp1/1pnp3P/PPPP1P1P/PPP1PPP1/1PPP1PPP/PPPPPPPP/PPPPPPPP b kq - 0 7")
|
||||
val game = fenToGame(position, Horde)(Square.H8, Square.H6).get
|
||||
val game = fenToGame(position, Horde)(Square.H8, Square.H6).get
|
||||
assertEquals(game._1.position.history.unmovedRooks, UnmovedRooks(Set(Square.A8)))
|
||||
assertEquals(game._1.position.history.castles, Castles(false, false, false, true))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user