-
-
Notifications
You must be signed in to change notification settings - Fork 227
Expand file tree
/
Copy pathInsufficientMatingMaterial.scala
More file actions
114 lines (105 loc) · 4.4 KB
/
Copy pathInsufficientMatingMaterial.scala
File metadata and controls
114 lines (105 loc) · 4.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package chess
/** Utility methods for helping to determine whether a board is a draw or a draw
* on a player flagging.
*
* See http://www.e4ec.org/immr.html
*/
object InsufficientMatingMaterial:
// verify if there are at least two bishops of opposite color
// no matter which sides they are on
def bishopsOnOppositeColors(board: Board): Boolean =
board.bishops.intersects(Bitboard.lightSquares) &&
board.bishops.intersects(Bitboard.darkSquares)
/**
* Returns true if a pawn cannot progress forward because it is blocked by a pawn
* and it doesn't have any capture
*/
def pawnBlockedByPawn(pawn: Square, position: Position): Boolean =
position
.pieceAt(pawn)
.exists(p =>
p.is(Pawn) &&
position.withColor(p.color).generateMovesAt(pawn).isEmpty && {
val blockingPosition = pawn.nextRank(p.color)
blockingPosition.flatMap(position.pieceAt).exists(_.is(Pawn))
}
)
/**
* Returns whether some square in `destinations` can be reached by a king moving from `startSquare`,
* while avoiding all squares in `forbidden`.
*
* `destinations` must not contain `startSquare`, or any square in `forbidden`.
*/
def kingPathExists(startSquare: Square, destinations: Bitboard, forbidden: Bitboard): Option[Boolean] =
if destinations.intersects(forbidden.add(startSquare)) then None
else
@annotation.tailrec
def bfs(frontier: Bitboard, skip: Bitboard): Boolean =
if frontier.isEmpty then false
else if frontier.intersects(destinations) then true
else
val updatedSkip = skip | frontier
bfs(frontier.fold(Bitboard.empty)((acc, sq) => acc | sq.kingAttacks) & ~updatedSkip, updatedSkip)
Some(bfs(startSquare.bb, forbidden))
/**
* Checks if all pawns are locked, just with respect to each other. Other pieces that could allow the
* pawns to make captures are not considered.
*/
def allPawnsLocked(board: Board): Boolean =
List(White, Black).forall: color =>
board.squaresAttackedByPawns(color).isDisjoint(board.byPiece(!color, Pawn)) &&
board
.byPiece(color, Pawn)
.forall: pawnSq =>
pawnSq
.nextRank(color)
.exists: frontSq =>
board.pawns.contains(frontSq)
def kingPawnFortress(position: Position): Boolean =
val board = position.board
(board.kings | board.pawns) == board.occupied &&
allPawnsLocked(board) &&
(
List(White, Black).forall: color =>
val squaresAttackedByEnemyPawns = board.squaresAttackedByPawns(!color)
val squareOfKing = board.kingPosOf(color).get
!squaresAttackedByEnemyPawns.contains(squareOfKing) && !kingPathExists(
squareOfKing,
board.byPiece(!color, Pawn) & ~squaresAttackedByEnemyPawns,
board.byPiece(color, Pawn) | squaresAttackedByEnemyPawns
).get
) &&
position.enPassantSquare.isEmpty
/**
* Determines whether a board position is an automatic draw due to neither player
* being able to mate the other as informed by the traditional chess rules.
*/
def apply(position: Position): Boolean =
val board = position.board
(
board.kingsAndMinorsOnly &&
(board.nbPieces <= 3 || (board.kingsAndBishopsOnly && !bishopsOnOppositeColors(board)))
) || kingPawnFortress(position)
/**
* Determines whether a color does not have mating material. In general:
* King by itself is not mating material
* King + knight mates against king + any(rook, bishop, knight, pawn)
* King + bishop mates against king + any(bishop, knight, pawn)
* King + bishop(s) versus king + bishop(s) depends upon bishop square colors
* So this function returns true in three cases:
* - if color has only king
* - if color has king + knight and opponent has king + queen(s)
* - if color has king + bishop and opponent doesn't have:
* - opposite color bishop(s)
* - or knight(s) or pawn(s)
*/
def apply(position: Position, color: Color): Boolean =
import board.*
val board = position.board
inline def onlyKing = kingsOnlyOf(color)
inline def KN =
onlyOf(color, King, Knight) && count(color, Knight) == 1 && onlyOf(!color, King, Queen)
inline def KB =
onlyOf(color, King, Bishop) &&
!(bishopsOnOppositeColors(board) || byPiece(!color, Knight, Pawn).nonEmpty)
onlyKing || KN || KB || kingPawnFortress(position)