Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix recent regression #440

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/main/scala/Board.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ case class Board(
byRole,
byRoleOf,
colorAt,
fold,
foreach,
isCheck,
isOccupied,
kingOf,
Expand Down
14 changes: 7 additions & 7 deletions src/main/scala/ByColor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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
Expand Down
38 changes: 18 additions & 20 deletions src/main/scala/Divider.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
2 changes: 0 additions & 2 deletions src/main/scala/Game.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
23 changes: 11 additions & 12 deletions src/main/scala/Hash.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)(_ ^ _)
Expand All @@ -67,12 +60,18 @@ 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
Expand Down
14 changes: 7 additions & 7 deletions src/main/scala/Role.scala
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,15 @@ 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))
case Rook => copy(rook = f(rook))
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)
Expand All @@ -90,29 +90,29 @@ 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)
f(rook)
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)
f(Rook, rook)
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)
Expand Down
23 changes: 22 additions & 1 deletion src/main/scala/bitboard/Board.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -155,6 +154,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)

Expand Down
3 changes: 1 addition & 2 deletions src/main/scala/variant/Antichess.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions src/main/scala/variant/Variant.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down