From 02c45ca383e58e49992a32e46f36bf5e79e4c4a4 Mon Sep 17 00:00:00 2001 From: Thanh Le Date: Fri, 7 Jul 2023 00:08:18 +0200 Subject: [PATCH 1/4] Improve Hash by using manual foreach --- src/main/scala/Hash.scala | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/scala/Hash.scala b/src/main/scala/Hash.scala index 07e5437cd..86e2b36fa 100644 --- a/src/main/scala/Hash.scala +++ b/src/main/scala/Hash.scala @@ -41,7 +41,7 @@ object Hash extends OpaqueInt[Hash]: private val polyglotTable = ZobristConstants(0) private lazy val randomTable = ZobristConstants(16) - private def roleIndex(role: Role) = + private inline def roleIndex(inline role: Role) = role match case Pawn => 0 case Knight => 1 @@ -50,13 +50,6 @@ object Hash extends OpaqueInt[Hash]: case Queen => 4 case King => 5 - private def pieceIndex(piece: Piece): Int = - roleIndex(piece.role) * 2 + piece.color.fold(1, 0) - - // todo rename - private def pieceIndex(square: Square, piece: Piece): Int = - 64 * pieceIndex(piece) + square.hashCode - private def get(situation: Situation, table: ZobristConstants): Long = extension (xs: Iterable[Long]) def computeHash(seed: Long): Long = xs.fold(seed)(_ ^ _) @@ -67,12 +60,19 @@ object Hash extends OpaqueInt[Hash]: Option.when(0 < count && count <= 16 && role != King): table.crazyPocketMasks(16 * roleIndex(role) + count + colorshift) - val board = situation.board + import situation.board val hturn = situation.color.fold(table.whiteTurnMask, 0L) - val hpieces = board.pieces - .map((square, piece) => table.actorMasks(pieceIndex(square, piece))) - .computeHash(hturn) + val hpieces = + var m = hturn + board.byColor.foreach: (color, c) => + board.byRole.foreach: (role, r) => + val pi = 64 * (roleIndex(role) * 2 + color.fold(1, 0)) + (c & r).foreach: s => + val tm = table.actorMasks(pi + s.hashCode) + m ^= tm + m + val hcastling = if board.variant.allowsCastling then From 5287f8826dbe0cbce43ff9be24712c9832c998cb Mon Sep 17 00:00:00 2001 From: Thanh Le Date: Fri, 7 Jul 2023 00:20:28 +0200 Subject: [PATCH 2/4] Add fold* functions for Board --- src/main/scala/Board.scala | 2 ++ src/main/scala/Game.scala | 2 -- src/main/scala/Hash.scala | 1 - src/main/scala/bitboard/Board.scala | 22 ++++++++++++++++++++++ src/main/scala/variant/Antichess.scala | 3 +-- src/main/scala/variant/Variant.scala | 10 +++++----- 6 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/main/scala/Board.scala b/src/main/scala/Board.scala index 42f390429..709c79257 100644 --- a/src/main/scala/Board.scala +++ b/src/main/scala/Board.scala @@ -21,6 +21,8 @@ case class Board( byRole, byRoleOf, colorAt, + fold, + foreach, isCheck, isOccupied, kingOf, diff --git a/src/main/scala/Game.scala b/src/main/scala/Game.scala index 7f6b569c1..6ef7b24a9 100644 --- a/src/main/scala/Game.scala +++ b/src/main/scala/Game.scala @@ -85,8 +85,6 @@ case class Game( def apply(uci: Uci): Either[ErrorStr, (Game, MoveOrDrop)] = apply(uci) map { case (g, m) => g -> m } - inline def isStandardInit = board.pieces == chess.variant.Standard.pieces - inline def fullMoveNumber: FullMoveNumber = ply.fullMoveNumber inline def withBoard(inline b: Board) = copy(situation = situation.copy(board = b)) diff --git a/src/main/scala/Hash.scala b/src/main/scala/Hash.scala index 86e2b36fa..bdbe87dce 100644 --- a/src/main/scala/Hash.scala +++ b/src/main/scala/Hash.scala @@ -73,7 +73,6 @@ object Hash extends OpaqueInt[Hash]: m ^= tm m - val hcastling = if board.variant.allowsCastling then (situation.history.castles.toSeq.view zip table.castlingMasks) diff --git a/src/main/scala/bitboard/Board.scala b/src/main/scala/bitboard/Board.scala index 94ff17f4e..bd6509023 100644 --- a/src/main/scala/bitboard/Board.scala +++ b/src/main/scala/bitboard/Board.scala @@ -155,6 +155,28 @@ case class Board( m += s -> piece m.result + def fold[B](init: B)(f: (B, Color, Role) => B): B = + var m = init + byColor.foreach: (color, c) => + byRole.foreach: (role, r) => + (c & r).foreach: _ => + m = f(m, color, role) + m + + def fold[B](init: B)(f: (B, Color, Role, Square) => B): B = + var m = init + byColor.foreach: (color, c) => + byRole.foreach: (role, r) => + (c & r).foreach: s => + m = f(m, color, role, s) + m + + def foreach[U](f: (Color, Role, Square) => U): Unit = + byColor.foreach: (color, c) => + byRole.foreach: (role, r) => + (c & r).foreach: s => + f(color, role, s) + def piecesOf(c: Color): Map[Square, Piece] = pieceMap.filter((_, p) => p.color == c) diff --git a/src/main/scala/variant/Antichess.scala b/src/main/scala/variant/Antichess.scala index 5cc19db5c..227b499be 100644 --- a/src/main/scala/variant/Antichess.scala +++ b/src/main/scala/variant/Antichess.scala @@ -48,9 +48,8 @@ case object Antichess // In antichess, it is valuable for your opponent to have pieces. override def materialImbalance(board: Board): Int = - board.allPieces.foldLeft(0) { case (acc, Piece(color, _)) => + board.fold(0): (acc, color, _) => acc + color.fold(-2, 2) - } // In antichess, there is no checkmate condition therefore a player may only draw either by agreement, // blockade or stalemate. Only one player can win if the only remaining pieces are two knights diff --git a/src/main/scala/variant/Variant.scala b/src/main/scala/variant/Variant.scala index 8d0b3f44c..42a9b1b4c 100644 --- a/src/main/scala/variant/Variant.scala +++ b/src/main/scala/variant/Variant.scala @@ -102,11 +102,11 @@ abstract class Variant private[variant] ( /** Returns the material imbalance in pawns (overridden in Antichess) */ def materialImbalance(board: Board): Int = - board.allPieces.foldLeft(0) { case (acc, Piece(color, role)) => - Role.valueOf(role).fold(acc) { value => - acc + value * color.fold(1, -1) - } - } + board.fold(0): (acc, color, role) => + Role + .valueOf(role) + .fold(acc): value => + acc + value * color.fold(1, -1) /** Returns true if neither player can win. The game should end immediately. */ From 53a7c396bf44e61cfb8dd2a4fae7fb2099b83604 Mon Sep 17 00:00:00 2001 From: Thanh Le Date: Sat, 8 Jul 2023 00:16:42 +0200 Subject: [PATCH 3/4] Use inline to squeze to fix regression from 15.3.6 --- src/main/scala/ByColor.scala | 14 +++++++------- src/main/scala/Role.scala | 14 +++++++------- src/main/scala/bitboard/Board.scala | 1 - 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/main/scala/ByColor.scala b/src/main/scala/ByColor.scala index 03101af4c..297ba67f0 100644 --- a/src/main/scala/ByColor.scala +++ b/src/main/scala/ByColor.scala @@ -7,19 +7,19 @@ import alleycats.Zero case class ByColor[A](white: A, black: A): - def apply(color: Color) = if color.white then white else black + inline def apply(inline color: Color) = if color.white then white else black - def apply[B](color: Color)(f: A => B): B = if color.white then f(white) else f(black) + inline def apply[B](inline color: Color)(f: A => B): B = if color.white then f(white) else f(black) - def update(color: Color, f: A => A): ByColor[A] = + inline def update(inline color: Color, f: A => A): ByColor[A] = if color.white then copy(white = f(white)) else copy(black = f(black)) - def update(color: Color, f: A => Option[A]): Option[ByColor[A]] = + inline def update(inline color: Color, f: A => Option[A]): Option[ByColor[A]] = if color.white then f(white).map(w => copy(white = w)) else f(black).map(b => copy(black = b)) - def map[B](fw: A => B, fb: A => B) = copy(white = fw(white), black = fb(black)) + def map[B](fw: A => B, fb: A => 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)) @@ -54,12 +54,12 @@ case class ByColor[A](white: A, black: A): def flip: ByColor[A] = copy(white = black, black = white) - def findColor(pred: A => Boolean): Option[Color] = + inline def findColor(pred: A => Boolean): Option[Color] = if pred(white) then White.some else if pred(black) then Black.some else None - def find(pred: A => Boolean): Option[A] = + inline def find(pred: A => Boolean): Option[A] = if pred(white) then white.some else if pred(black) then black.some else None diff --git a/src/main/scala/Role.scala b/src/main/scala/Role.scala index f648cba94..62488a8ee 100644 --- a/src/main/scala/Role.scala +++ b/src/main/scala/Role.scala @@ -73,7 +73,7 @@ case class ByRole[A](pawn: A, knight: A, bishop: A, rook: A, queen: A, king: A): case Queen => queen case King => king - def update(role: Role, f: A => A): ByRole[A] = role match + inline def update(role: Role, f: A => A): ByRole[A] = role match case Pawn => copy(pawn = f(pawn)) case Knight => copy(knight = f(knight)) case Bishop => copy(bishop = f(bishop)) @@ -81,7 +81,7 @@ case class ByRole[A](pawn: A, knight: A, bishop: A, rook: A, queen: A, king: A): case Queen => copy(queen = f(queen)) case King => copy(king = f(king)) - def find(f: A => Boolean): Option[A] = + inline def find(f: A => Boolean): Option[A] = if f(pawn) then Some(pawn) else if f(knight) then Some(knight) else if f(bishop) then Some(bishop) @@ -90,13 +90,13 @@ case class ByRole[A](pawn: A, knight: A, bishop: A, rook: A, queen: A, king: A): else if f(king) then Some(king) else None - def fold[B](z: B)(f: (B, A) => B): B = + inline def fold[B](z: B)(f: (B, A) => B): B = f(f(f(f(f(f(z, pawn), knight), bishop), rook), queen), king) - def fold[B](z: B)(f: (B, Role, A) => B): B = + inline def fold[B](z: B)(f: (B, Role, A) => B): B = f(f(f(f(f(f(z, Pawn, pawn), Knight, knight), Bishop, bishop), Rook, rook), Queen, queen), King, king) - def foreach[U](f: A => U): Unit = + inline def foreach[U](f: A => U): Unit = f(pawn) f(knight) f(bishop) @@ -104,7 +104,7 @@ case class ByRole[A](pawn: A, knight: A, bishop: A, rook: A, queen: A, king: A): f(queen) f(king) - def foreach[U](f: (Role, A) => U): Unit = + inline def foreach[U](f: (Role, A) => U): Unit = f(Pawn, pawn) f(Knight, knight) f(Bishop, bishop) @@ -112,7 +112,7 @@ case class ByRole[A](pawn: A, knight: A, bishop: A, rook: A, queen: A, king: A): f(Queen, queen) f(King, king) - def findRole(f: A => Boolean): Option[Role] = + inline def findRole(f: A => Boolean): Option[Role] = if f(pawn) then Some(Pawn) else if f(knight) then Some(Knight) else if f(bishop) then Some(Bishop) diff --git a/src/main/scala/bitboard/Board.scala b/src/main/scala/bitboard/Board.scala index bd6509023..c3baa37ab 100644 --- a/src/main/scala/bitboard/Board.scala +++ b/src/main/scala/bitboard/Board.scala @@ -98,7 +98,6 @@ case class Board( byRole.map(_ & notMask) ) - def roles: Role => Bitboard = byRole.apply def byRoleOf(color: Color): ByRole[Bitboard] = byRole.map(_ & byColor(color)) // put a piece to an empty square From 4d545cec25f47ec5aa35c2b6a302f006d4178224 Mon Sep 17 00:00:00 2001 From: Thanh Le Date: Sat, 8 Jul 2023 09:50:59 +0200 Subject: [PATCH 4/4] Fix Divider regression --- src/main/scala/Divider.scala | 38 +++++++++++++++++------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/main/scala/Divider.scala b/src/main/scala/Divider.scala index 6c6034e97..d8e151959 100644 --- a/src/main/scala/Divider.scala +++ b/src/main/scala/Divider.scala @@ -22,33 +22,33 @@ object Divider: val indexedBoards: List[(Board, Int)] = boards.zipWithIndex - val midGame = indexedBoards.collectFirst: - case (board, index) - if (majorsAndMinors(board) <= 10 || - backrankSparse(board) || - mixedness(board) > 150) => - index + val midGame = indexedBoards.foldLeft(none[Int]): + case (None, (board, index)) => + (majorsAndMinors(board) <= 10 || + backrankSparse(board) || + mixedness(board) > 150) option index + case (found, _) => found val endGame = - midGame.fold(none): midIndex => - indexedBoards - .drop(midIndex) - .collectFirst: - case (board, index) if (majorsAndMinors(board) <= 6) => index + if midGame.isDefined then + indexedBoards.foldLeft(none[Int]): + case (found: Some[?], _) => found + case (_, (board, index)) => (majorsAndMinors(board) <= 6) option index + else None Division( - Ply from midGame, + Ply from midGame.filter(m => endGame.fold(true)(m < _)), Ply from endGame, Ply(boards.size) ) private def majorsAndMinors(board: Board): Int = - (board.queens | board.rooks | board.bishops | board.knights).count + (board.occupied & ~(board.kings | board.pawns)).count // Sparse back-rank indicates that pieces have been developed private def backrankSparse(board: Board): Boolean = - (Bitboard.rank(Rank.First) & board.white).count < 4 || - (Bitboard.rank(Rank.Eighth) & board.black).count < 4 + (Bitboard.firstRank & board.white).count < 4 || + (Bitboard.lastRank & board.black).count < 4 private def score(white: Int, black: Int, y: Int): Int = ((white, black): @switch) match @@ -95,9 +95,7 @@ object Divider: var white = 0 var black = 0 region.foreach: s => - board - .colorAt(s) - .foreach: v => - if v == White then white += 1 - else black += 1 + board(s).foreach: v => + if v is White then white = white + 1 + else black = black + 1 mix + score(white, black, region.head.rank.index + 1)