ychess is a chess implementation written in nim.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

838 lines
31 KiB

  1. import tables
  2. from strutils import parseInt
  3. type
  4. Color* = enum
  5. ## `Color` describes the possible color of players.
  6. Black = -1,
  7. White = 1
  8. Board* = array[0..119, int] ## \
  9. ## `Board` saves the position of the chess pieces.
  10. Moved* = array[0..119, bool] ## \
  11. ## `Moved` saves the position of squares a piece moved on or from.
  12. CastleRights = tuple
  13. ## `CastleRights` contains the rights to castling for each player.
  14. wk: bool # `wk` describes White kingside castle
  15. wq: bool # `wq` describes White queenside castle
  16. bk: bool # `bk` describes Black kingside castle
  17. bq: bool # `bq` describes Black queenside castle
  18. Game* = object
  19. ## `Game` stores all important information of a chess game.
  20. board*: Board
  21. moved: Moved
  22. toMove*: Color
  23. previousBoard: seq[Board]
  24. previousCastleRights: seq[CastleRights]
  25. fiftyMoveCounter: int
  26. Move* = object
  27. ## `Move` stores all important information for a move.
  28. start: int
  29. dest: int
  30. color: Color
  31. prom: int
  32. PieceAmount = tuple
  33. ## `PieceAmount` describes the number of pieces of a certain type a/both
  34. ## player/s has/have.
  35. p: int # `p` describes the amount of pawns.
  36. n: int # `n` describes the amount of knights.
  37. b: int # `b` describes the amount of bishops.
  38. r: int # `r` describes the amount of rooks.
  39. q: int # `q` describes the amount of queens.
  40. const
  41. Block* = 999 ## \
  42. ## `Block` is the value assigned to empty blocked fields in a board.
  43. WPawn* = 1
  44. ## `WPawn` is the value assigned to a square in a board with a white pawn.
  45. WKnight* = 2 ## \
  46. ## `WKnight` is the value assigned to a square in a board with a white
  47. ## knight.
  48. WBishop* = 3 ## \
  49. ## `WBishop` is the value assigned to a square in a board with a white
  50. ## bishop.
  51. WRook* = 4 ## \
  52. ## `WRook` is the value assigned to a square in a board with a white rook.
  53. WQueen* = 5 ## \
  54. ## `WQueen` is the value assigned to a square in a board with a white
  55. ## queen.
  56. WKing* = 6 ## \
  57. ## `WKing` is the value assigned to a square in a board with a white king.
  58. WEnPassant* = 7 ## \
  59. ## `WEnPassant` is assigned to a square in a board with an invisible white
  60. ## en passant pawn.
  61. BPawn* = -WPawn ## \
  62. ## `BPawn` is the value assigned to a square in a board with a black pawn.
  63. BKnight* = -WKnight ## \
  64. ## `BKnight` is the value assigned to a square in a board with a black\
  65. ## knight.
  66. BBishop* = -WBishop ## \
  67. ## `BBishop` is the value assigned to a square in a board with a black\
  68. ## bishop.
  69. BRook* = -WRook ## \
  70. ## `BRook` is the value assigned to a square in a board with a black rook.
  71. BQueen* = -WQueen ## \
  72. ## `BQueen` is the value assigned to a square in a board with a black queen.
  73. BKing* = -WKing ## \
  74. ## `BKing` is the value assigned to a square in a board with a black king.
  75. BEnPassant* = -WEnPassant ## \
  76. ## `BEnPassant` is assigned to a square in a board with an invisible black
  77. ## en passant pawn.
  78. N = 10 ## `N` describes a move a field up the board from whites perspective.
  79. S = -N ## `S` describes a move a field down the board from whites perspective.
  80. W = 1 ## `W` describes a move a field to the left from whites perspective.
  81. E = -W ## `E` describes a move a field to the right from whites perspective.
  82. # Directions for the pieces. Special moves are in separate arrays.
  83. Knight_Moves = [N+N+E, N+N+W, E+E+N, E+E+S, S+S+E, S+S+W, W+W+N, W+W+S] ## \
  84. ## `Knight_Moves` describes the possible knight moves.
  85. Bishop_Moves = [N+E, N+W, S+E, S+W] ## \
  86. ## `Bishop_Moves` describes the possible 1 field distance bishop moves.
  87. Rook_Moves = [N, E, S, W] ## \
  88. ## `Rook_Moves` describes the possible 1 field distance rook moves.
  89. Queen_Moves = [N, E, S, W, N+E, N+W, S+E, S+W] ## \
  90. ## `Queen_Moves` describes the possible 1 field distance queen moves.
  91. King_Moves = [N, E, S, W, N+E, N+W, S+E, S+W] ## \
  92. ## `King_Moves` describes the possible 1 field distance king moves.
  93. King_Moves_White_Castle = [E+E, W+W] ## \
  94. ## `King_Moves` describes the possible king moves for castling.
  95. Pawn_Moves_White = [N] ## \
  96. ## `Pawn_Moves_White` describes the possible 1 field distance pawn moves
  97. ## from whites perspective that are not attacks.
  98. Pawn_Moves_White_Double = [N+N] ## \
  99. ## `Pawn_Moves_White_Double` describes the possible pawn 2 field distance
  100. ## moves from whites perspective.
  101. Pawn_Moves_White_Attack = [N+E, N+W] ## \
  102. ## `Pawn_Moves_White` describes the possible 1 field distance pawn moves
  103. ## from whites perspective that are ttacks.
  104. InsufficientMaterial: array[4, PieceAmount] = [
  105. (0, 0, 0, 0, 0), # only kings
  106. (0, 0, 1, 0, 0), # knight only
  107. (0, 1, 0, 0, 0), # bishop only
  108. (0, 2, 0, 0, 0) # 2 knights
  109. ] ## `InsufficientMaterial` describes the pieces where no checkmate can be
  110. ## forced
  111. let
  112. PieceChar = {
  113. 0: " ",
  114. WPawn: "P",
  115. WKnight: "N",
  116. WBishop: "B",
  117. WRook: "R",
  118. WQueen: "Q",
  119. WKing: "K",
  120. WEnPassant: " ",
  121. BPawn: "p",
  122. BKnight: "n",
  123. BBishop: "b",
  124. BRook: "r",
  125. BQueen: "q",
  126. BKing: "k",
  127. BEnPassant: " ",
  128. }.newTable ## \
  129. ## `PieceChar` describes the representation for the pieceIDs for the cli.
  130. FileChar = {
  131. "a": 7,
  132. "b": 6,
  133. "c": 5,
  134. "d": 4,
  135. "e": 3,
  136. "f": 2,
  137. "g": 1,
  138. "h": 0
  139. }.newTable ## \
  140. # `FileChar` maps the files of the chessboard to numbers for better
  141. # conversion.
  142. proc fieldToInd*(file: string, line: int): int =
  143. ## Calculate and return board index from `file` and `line` of a chess board.
  144. ## Returns -1 if the `field` was not input correct.
  145. try:
  146. return 1+(line+1)*10+FileChar[file]
  147. except IndexDefect, ValueError:
  148. return -1
  149. proc fieldToInd*(field: string): int =
  150. ## Calculate and return board index from `field` of a chess board.
  151. ## Returns -1 if the `field` was not input correct.
  152. try:
  153. return fieldToInd($field[0], parseInt($field[1]))
  154. except IndexDefect, ValueError:
  155. return -1
  156. proc indToField*(ind: int): string =
  157. ## Calculate and returns field name from board index `ind`.
  158. let line = (int)ind/10-1
  159. let file_ind = (ind)%%10-1
  160. for file, i in FileChar:
  161. if FileChar[file] == file_ind:
  162. return $file & $line
  163. proc getMove*(start: int, dest: int, prom: int, color: Color): Move =
  164. ## Get a move object of the `color` player from `start` to `dest` with an
  165. ## eventual promition to `prom`.
  166. var move = Move(start: start, dest: dest, prom: prom * ord(color), color: color)
  167. if (WKnight > prom or WQueen < prom):
  168. move.prom = WQueen
  169. return move
  170. proc getMove*(start: int, dest: int, color: Color): Move =
  171. ## Get a move object of the `color` player from `start` to `dest` with
  172. ## automatic promition to queen.
  173. var move = Move(start: start, dest: dest, prom: WQueen * ord(color), color: color)
  174. return move
  175. proc notationToMove*(notation: string, color: Color): Move =
  176. ## Convert and return simplified algebraic chess `notation` to a move object,
  177. ## color of player is `color`.
  178. var move: Move
  179. var start = fieldToInd(notation[0..1])
  180. var dest = fieldToInd(notation[2..3])
  181. move = getMove(start, dest, color)
  182. if (len(notation) > 4):
  183. var promStr = $notation[4]
  184. var prom: int
  185. case promStr:
  186. of "Q":
  187. prom = WQueen * ord(color)
  188. of "R":
  189. prom = WRook * ord(color)
  190. of "B":
  191. prom = WBishop * ord(color)
  192. of "N":
  193. prom = WKnight * ord(color)
  194. move = getMove(start, dest, prom, color)
  195. return move
  196. proc initBoard(): Board =
  197. ## Create and return a board with pieces in starting position.
  198. let board = [
  199. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block,
  200. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block,
  201. Block, WRook, WKnight, WBishop, WKing, WQueen, WBishop, WKnight, WRook, Block,
  202. Block, WPawn, WPawn, WPawn, WPawn, WPawn, WPawn, WPawn, WPawn, Block,
  203. Block, 0, 0, 0, 0, 0, 0, 0, 0, Block,
  204. Block, 0, 0, 0, 0, 0, 0, 0, 0, Block,
  205. Block, 0, 0, 0, 0, 0, 0, 0, 0, Block,
  206. Block, 0, 0, 0, 0, 0, 0, 0, 0, Block,
  207. Block, BPawn, BPawn, BPawn, BPawn, BPawn, BPawn, BPawn, BPawn, Block,
  208. Block, BRook, BKnight, BBishop, BKing, BQueen, BBishop, BKnight, BRook, Block,
  209. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block,
  210. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block]
  211. return board
  212. proc initBoard(board: array[0..63, int]): Board =
  213. ## Create and return a board with pieces in position of choice, described in
  214. ## `board`.
  215. let board = [
  216. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block,
  217. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block,
  218. Block, board[0], board[1], board[2], board[3], board[4], board[5],
  219. board[6], board[7], Block,
  220. Block, board[8], board[9], board[10], board[11], board[12], board[13],
  221. board[14], board[15], Block,
  222. Block, board[16], board[17], board[18], board[19], board[20], board[
  223. 21], board[22], board[23], Block,
  224. Block, board[24], board[25], board[26], board[27], board[28], board[
  225. 29], board[30], board[31], Block,
  226. Block, board[32], board[33], board[34], board[35], board[36], board[
  227. 37], board[38], board[39], Block,
  228. Block, board[40], board[41], board[42], board[43], board[44], board[
  229. 45], board[46], board[47], Block,
  230. Block, board[48], board[49], board[50], board[51], board[52], board[
  231. 53], board[54], board[55], Block,
  232. Block, board[56], board[57], board[58], board[59], board[60], board[
  233. 61], board[62], board[63], Block,
  234. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block,
  235. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block]
  236. return board
  237. proc initMoved(): Moved =
  238. ## Create and return a board of pieces moved.
  239. var moved: Moved
  240. return moved
  241. proc initGame*(): Game =
  242. ## Create and return a Game object.
  243. let game = Game(board: initBoard(), moved: initMoved(),
  244. to_move: Color.White, previousBoard: @[], previousCastleRights: @[],
  245. fiftyMoveCounter: 0)
  246. return game
  247. proc initGame*(board: array[0..63, int], color: Color): Game =
  248. ## Create ad return a Game object based on a position of choice.
  249. ## `board` describes the pieces, `color` the color that is about to move.
  250. let board = initBoard(board)
  251. let compare = initBoard()
  252. var moved = initMoved()
  253. var same_piece: bool
  254. for ind in board.low..board.high:
  255. same_piece = (board[ind] != compare[ind])
  256. moved[ind] = same_piece
  257. let game = Game(board: board, moved: moved,
  258. to_move: color, previousBoard: @[], previousCastleRights: @[],
  259. fiftyMoveCounter: 0)
  260. return game
  261. proc echoBoard*(game: Game, color: Color) =
  262. ## Prints out the given `board` with its pieces as characters and line
  263. ## indices from perspecive of `color`.
  264. var line_str = ""
  265. if (color == Color.Black):
  266. for i in countup(0, len(game.board)-1):
  267. if (game.board[i] == 999):
  268. continue
  269. line_str &= PieceChar[game.board[i]] & " "
  270. if ((i+2) %% 10 == 0):
  271. line_str &= $((int)((i)/10)-1) & "\n"
  272. echo line_str
  273. echo "h g f e d c b a"
  274. else:
  275. for i in countdown(len(game.board)-1, 0):
  276. if (game.board[i] == 999):
  277. continue
  278. line_str &= PieceChar[game.board[i]] & " "
  279. if ((i-1) %% 10 == 0):
  280. line_str &= $((int)((i)/10)-1) & "\n"
  281. echo line_str
  282. echo "a b c d e f g h"
  283. proc genCastleRights(moved: Moved): CastleRights =
  284. ## Generate and return rights to castle from given `moved`
  285. let wk = not moved[fieldToInd("e1")] and not moved[fieldToInd("h1")]
  286. let wq = not moved[fieldToInd("e1")] and not moved[fieldToInd("a1")]
  287. let bk = not moved[fieldToInd("e8")] and not moved[fieldToInd("h8")]
  288. let bq = not moved[fieldToInd("e8")] and not moved[fieldToInd("a8")]
  289. return (wk, wq, bk, bq)
  290. proc genPawnAttackDests(game: Game, field: int, color: Color): seq[int] =
  291. ## Generate possible attack destinations for a pawn with specific `color`
  292. ## located at index `field` of `game`.
  293. ## Returns a sequence of possible indices to move to.
  294. if (not field in game.board.low..game.board.high):
  295. return @[]
  296. var res = newSeq[int]()
  297. var dest: int
  298. var target: int
  299. for attacks in Pawn_Moves_White_Attack:
  300. dest = field + (attacks * ord(color))
  301. if (not dest in game.board.low..game.board.high):
  302. continue
  303. target = game.board[dest]
  304. if (target == 999 or ord(color) * target >= 0):
  305. continue
  306. res.add(dest)
  307. return res
  308. proc genPawnDoubleDests(game: Game, field: int, color: Color): seq[int] =
  309. ## Generate possible double destinations for a pawn with specific `color`
  310. ## located at index `field` of `game`.
  311. ## Returns a sequence of possible indices to move to.
  312. if (not field in game.board.low..game.board.high):
  313. return @[]
  314. var res = newSeq[int]()
  315. var dest: int
  316. var target: int
  317. for doubles in Pawn_Moves_White_Double:
  318. dest = field + doubles * ord(color)
  319. if (not dest in game.board.low..game.board.high):
  320. continue
  321. target = game.board[dest]
  322. if (game.moved[field] or (target != 0) or (
  323. game.board[dest+(S*ord(color))] != 0)):
  324. continue
  325. res.add(dest)
  326. return res
  327. proc genPawnDests(game: Game, field: int, color: Color): seq[int] =
  328. ## Generate possible destinations for a pawn with specific `color` located at
  329. ## index `field` of `game`.
  330. ## Returns a sequence of possible indices to move to.
  331. if (not field in game.board.low..game.board.high):
  332. return @[]
  333. var res = newSeq[int]()
  334. var dest: int
  335. var target: int
  336. for move in Pawn_Moves_White:
  337. dest = field + move * ord(color)
  338. if (not dest in game.board.low..game.board.high):
  339. continue
  340. target = game.board[dest]
  341. if (target != 0 and target != ord(color) * WEnPassant):
  342. continue
  343. res.add(dest)
  344. res.add(game.genPawnAttackDests(field, color))
  345. res.add(game.genPawnDoubleDests(field, color))
  346. return res
  347. proc genKnightDests(game: Game, field: int, color: Color): seq[int] =
  348. ## Generate possible destinations for a knight with specific `color` located
  349. ## at index `field` of `game`.
  350. ## Returns a sequence of possible indices to move to.
  351. if (not field in game.board.low..game.board.high):
  352. return @[]
  353. var res = newSeq[int]()
  354. var dest: int
  355. var target: int
  356. for move in Knight_Moves:
  357. dest = field + move
  358. if (not dest in game.board.low..game.board.high):
  359. continue
  360. target = game.board[dest]
  361. if (target == 999 or (ord(color) * target > 0 and ord(color) * target != WEnPassant)):
  362. continue
  363. res.add(dest)
  364. return res
  365. proc genBishopDests(game: Game, field: int, color: Color): seq[int] =
  366. ## Generate possible destinations for a bishop with specific `color` located
  367. ## at index `field` of `game`.
  368. ## Returns a sequence of possible indices to move to.
  369. if (not field in game.board.low..game.board.high):
  370. return @[]
  371. var res = newSeq[int]()
  372. var dest: int
  373. var target: int
  374. for move in Bishop_Moves:
  375. dest = field+move
  376. if (not dest in game.board.low..game.board.high):
  377. continue
  378. target = game.board[dest]
  379. while (target != 999 and (ord(color) * target <= 0) or target ==
  380. WEnPassant or target == -WEnPassant):
  381. res.add(dest)
  382. if (ord(color) * target < 0 and ord(color) * target > -WEnPassant):
  383. break
  384. dest = dest+move
  385. target = game.board[dest]
  386. return res
  387. proc genRookDests(game: Game, field: int, color: Color): seq[int] =
  388. ## Generate possible destinations for a rook with specific `color` located at
  389. ## index `field` of `game`.
  390. ## Returns a sequence of possible indices to move to.
  391. if (not field in game.board.low..game.board.high):
  392. return @[]
  393. var res = newSeq[int]()
  394. var dest: int
  395. var target: int
  396. for move in Rook_Moves:
  397. dest = field+move
  398. if (not dest in game.board.low..game.board.high):
  399. continue
  400. target = game.board[dest]
  401. while (target != 999 and (ord(color) * target <= 0) or target ==
  402. WEnPassant or target == -WEnPassant):
  403. res.add(dest)
  404. if (ord(color) * target < 0 and ord(color) * target > -WEnPassant):
  405. break
  406. dest = dest+move
  407. target = game.board[dest]
  408. return res
  409. proc genQueenDests(game: Game, field: int, color: Color): seq[int] =
  410. ## Generate possible destinations for a queen with specific `color` located
  411. ## at index `field` of `game`.
  412. ## Returns a sequence of possible indices to move to.
  413. if (not field in game.board.low..game.board.high):
  414. return @[]
  415. var res = newSeq[int]()
  416. var dest: int
  417. var target: int
  418. for move in Queen_Moves:
  419. dest = field+move
  420. if (not dest in game.board.low..game.board.high):
  421. continue
  422. target = game.board[dest]
  423. while (target != 999 and (ord(color) * target <= 0) or target ==
  424. WEnPassant or target == -WEnPassant):
  425. res.add(dest)
  426. if (ord(color) * target < 0 and ord(color) * target > -WEnPassant):
  427. break
  428. dest = dest+move
  429. target = game.board[dest]
  430. return res
  431. proc genKingCastleDest(game: Game, field: int, color: Color): seq[int] =
  432. ## Generate possible castle destinations for a king with specific `color`
  433. ## located at index `field` of `game`
  434. ## Returns a sequence of possible indices to move to.
  435. if (not field in game.board.low..game.board.high):
  436. return @[]
  437. var res = newSeq[int]()
  438. var dest: int
  439. var target: int
  440. var half_dest: int
  441. var half_target: int
  442. for castle in King_Moves_White_Castle:
  443. dest = field + castle
  444. if (not dest in game.board.low..game.board.high):
  445. continue
  446. target = game.board[dest]
  447. half_dest = field + (int)castle/2
  448. half_target = game.board[half_dest]
  449. if (target == 999 or (target != 0)):
  450. continue
  451. if (half_target == 999 or (half_target != 0)):
  452. continue
  453. res.add(dest)
  454. return res
  455. proc genKingDests(game: Game, field: int, color: Color): seq[int] =
  456. ## Generate possible destinations for a king with specific `color`
  457. ## located at index `field` of `game`.
  458. ## Returns a sequence of possible indices to move to.
  459. if (not field in game.board.low..game.board.high):
  460. return @[]
  461. var res = newSeq[int]()
  462. var dest: int
  463. var target: int
  464. for move in King_Moves:
  465. dest = field + move
  466. if (not dest in game.board.low..game.board.high):
  467. continue
  468. target = game.board[dest]
  469. if (target == 999 or (ord(color) * target > 0 and ord(color) * target != WEnPassant)):
  470. continue
  471. res.add(dest)
  472. res.add(game.genKingCastleDest(field, color))
  473. return res
  474. proc pieceOn(game: Game, color: Color, sequence: seq[int],
  475. pieceID: int): bool =
  476. ## Returns true if the `PieceID` of a given `color` is in `sequence` else
  477. ## wrong.
  478. for check in sequence:
  479. if game.board[check] == ord(color) * -1 * pieceID:
  480. return true
  481. return false
  482. proc isAttacked(game: Game, position: int, color: Color): bool =
  483. ## Returns true if a `position` in a `game` is attacked by the opposite
  484. ## color of `color`.
  485. var attacked = false
  486. attacked = attacked or game.pieceOn(color, game.genPawnAttackDests(
  487. position, color), WPawn)
  488. attacked = attacked or game.pieceOn(color, game.genQueenDests(position,
  489. color), WQueen)
  490. attacked = attacked or game.pieceOn(color, game.genKingDests(position,
  491. color), WKing)
  492. attacked = attacked or game.pieceOn(color, game.genRookDests(position,
  493. color), WRook)
  494. attacked = attacked or game.pieceOn(color, game.genBishopDests(position,
  495. color), WBishop)
  496. attacked = attacked or game.pieceOn(color, game.genKnightDests(position,
  497. color), WKnight)
  498. return attacked
  499. proc isInCheck*(game: Game, color: Color): bool =
  500. ## Returns true if the king of a given `color` is in check in a `game`.
  501. var king_pos: int
  502. for i in countup(0, game.board.high):
  503. if game.board[i] == ord(color) * WKing:
  504. king_pos = i
  505. return game.isAttacked(king_pos, color)
  506. proc uncheckedMove(game: var Game, start: int, dest: int): bool {.discardable.} =
  507. ## Moves a piece if possible from `start` position to `dest` position in a
  508. ## `game`.
  509. let piece = game.board[start]
  510. game.board[start] = 0
  511. game.board[dest] = piece
  512. game.moved[start] = true
  513. game.moved[dest] = true
  514. return true
  515. proc moveLeadsToCheck(game: Game, start: int, dest: int,
  516. color: Color): bool =
  517. ## Returns true if a move from `start` to `dest` in a `game` puts the `color`
  518. ## king in check.
  519. var check = game
  520. check.uncheckedMove(start, dest)
  521. return check.isInCheck(color)
  522. proc genPawnPromotion(move: Move, color: Color): seq[Move] =
  523. ## Generate all possible promotions of a `move` by `color`.
  524. var promotions = newSeq[Move]()
  525. let start = move.start
  526. let dest = move.dest
  527. if (90 < dest and dest < 99) or (20 < dest and dest < 29):
  528. for piece in WKnight..WQueen:
  529. promotions.add(getMove(start, dest, piece, color))
  530. return promotions
  531. proc genLegalPawnMoves(game: Game, field: int, color: Color): seq[Move] =
  532. ## Generates all legal pawn moves in a `game` starting from `field` for a
  533. ## `color`.
  534. if game.board[field] != WPawn * ord(color):
  535. return @[]
  536. var res = newSeq[Move]()
  537. var moves = game.genPawnDests(field, color)
  538. for dest in moves:
  539. if not game.moveLeadsToCheck(field, dest, color):
  540. var promotions = genPawnPromotion(getMove(field, dest, color), color)
  541. if promotions != @[]:
  542. res.add(promotions)
  543. else:
  544. res.add(getMove(field, dest, color))
  545. return res
  546. proc genLegalKnightMoves(game: Game, field: int, color: Color): seq[Move] =
  547. ## Generates all legal knight moves in a `game` starting from `field` for a
  548. ## `color`.
  549. if game.board[field] != WKnight * ord(color):
  550. return @[]
  551. var res = newSeq[Move]()
  552. var moves = game.genKnightDests(field, color)
  553. for dest in moves:
  554. if not game.moveLeadsToCheck(field, dest, color):
  555. res.add(getMove(field, dest, color))
  556. return res
  557. proc genLegalBishopMoves(game: Game, field: int, color: Color): seq[Move] =
  558. ## Generates all legal bishop moves in a `game` starting from `field` for a
  559. ## `color`.
  560. if game.board[field] != WBishop * ord(color):
  561. return @[]
  562. var res = newSeq[Move]()
  563. var moves = game.genBishopDests(field, color)
  564. for dest in moves:
  565. if not game.moveLeadsToCheck(field, dest, color):
  566. res.add(getMove(field, dest, color))
  567. return res
  568. proc genLegalRookMoves(game: Game, field: int, color: Color): seq[Move] =
  569. ## Generates all legal rook moves in a `game` starting from `field` for a
  570. ## `color`.
  571. if game.board[field] != WRook * ord(color):
  572. return @[]
  573. var res = newSeq[Move]()
  574. var moves = game.genRookDests(field, color)
  575. for dest in moves:
  576. if not game.moveLeadsToCheck(field, dest, color):
  577. res.add(getMove(field, dest, color))
  578. return res
  579. proc genLegalQueenMoves(game: Game, field: int, color: Color): seq[Move] =
  580. ## Generates all legal queen moves in a `game` starting from `field` for a
  581. ## `color`.
  582. if game.board[field] != WQueen * ord(color):
  583. return @[]
  584. var res = newSeq[Move]()
  585. var moves = game.genQueenDests(field, color)
  586. for dest in moves:
  587. if not game.moveLeadsToCheck(field, dest, color):
  588. res.add(getMove(field, dest, color))
  589. return res
  590. proc genLegalKingMoves(game: Game, field: int, color: Color): seq[Move] =
  591. ## Generates all legal king moves in a `game` starting from `field` for a
  592. ## `color`.
  593. if game.board[field] != WKing * ord(color):
  594. return @[]
  595. var res = newSeq[Move]()
  596. var moves = game.genKingDests(field, color)
  597. for dest in moves:
  598. if field - dest == W+W and game.isAttacked(dest+W, color):
  599. continue
  600. if field - dest == E+E and game.isAttacked(dest+E, color):
  601. continue
  602. if not game.moveLeadsToCheck(field, dest, color):
  603. res.add(getMove(field, dest, color))
  604. return res
  605. proc genLegalMoves*(game: Game, field: int, color: Color): seq[Move] =
  606. ## Generates all legal moves in a `game` starting from `field` for a `color`.
  607. var legal_moves = newSeq[Move]()
  608. var target = ord(color) * game.board[field]
  609. if 0 < target and target < WEnPassant:
  610. legal_moves = case target:
  611. of WPawn:
  612. game.genLegalPawnMoves(field, color)
  613. of WKnight:
  614. game.genLegalKnightMoves(field, color)
  615. of WBishop:
  616. game.genLegalBishopMoves(field, color)
  617. of WRook:
  618. game.genLegalRookMoves(field, color)
  619. of WQueen:
  620. game.genLegalQueenMoves(field, color)
  621. of WKing:
  622. game.genLegalKingMoves(field, color)
  623. else:
  624. @[]
  625. return legal_moves
  626. proc genLegalMoves*(game: Game, color: Color): seq[Move] =
  627. ## Generates all legal moves in a `game` for a `color`.
  628. var legal_moves = newSeq[Move]()
  629. for field in game.board.low..game.board.high:
  630. legal_moves.add(game.genLegalMoves(field, color))
  631. return legal_moves
  632. proc castling(game: var Game, kstart: int, dest_kingside: bool,
  633. color: Color): bool {.discardable.} =
  634. ## Tries to castle in a given `game` with the king of a given `color` from
  635. ## `kstart`.
  636. ## `dest_kingside` for kingside castling, else castling is queenside.
  637. ## This process checks for the legality of the move and performs the switch
  638. ## of `game.to_move`
  639. if game.toMove != color:
  640. return false
  641. var kdest = kstart
  642. var rstart: int
  643. var rdest: int
  644. if (dest_kingside):
  645. kdest = kstart + (E+E)
  646. rstart = kstart + (E+E+E)
  647. rdest = rstart + (W+W)
  648. else:
  649. rstart = kstart + (W+W+W+W)
  650. rdest = rstart + (E+E+E)
  651. kdest = kstart + (W+W)
  652. if not game.moved[kstart] and not game.moved[rstart]:
  653. var check = false
  654. if (dest_kingside):
  655. check = check or game.isAttacked(kstart, color)
  656. check = check or game.isAttacked(kstart+(E), color)
  657. check = check or game.isAttacked(kstart+(E+E), color)
  658. else:
  659. check = check or game.isAttacked(kstart, color)
  660. check = check or game.isAttacked(kstart+(W), color)
  661. check = check or game.isAttacked(kstart+(W+W), color)
  662. if check:
  663. return false
  664. game.uncheckedMove(kstart, kdest)
  665. game.uncheckedMove(rstart, rdest)
  666. game.toMove = Color(ord(game.toMove)*(-1))
  667. return true
  668. return false
  669. proc removeEnPassant(board: var Board, color: Color): void =
  670. ## Removes every en passant of given `color` from the `board`.
  671. for field in board.low..board.high:
  672. if board[field] == ord(color) * WEnPassant:
  673. board[field] = 0
  674. proc checkedMove*(game: var Game, move: Move): bool {.discardable.} =
  675. ## Tries to make a `move` in a given `game``.
  676. ## This process checks for the legality of the move and performs the switch
  677. ## of `game.to_move` with exception of castling (castling() switches).
  678. let start = move.start
  679. let dest = move.dest
  680. let color = move.color
  681. let prom = move.prom
  682. if (game.toMove != color or start == -1 or dest == -1):
  683. return false
  684. var sequence = newSeq[Move]()
  685. let piece = game.board[start]
  686. var createEnPassant = false
  687. var capturedEnPassant = false
  688. var fiftyMoveRuleReset = false
  689. var move: Move
  690. move = getMove(start, dest, color)
  691. if (piece == WPawn * ord(color)):
  692. createEnPassant = dest in game.genPawnDoubleDests(start, color)
  693. capturedEnPassant = (game.board[dest] == -1 * ord(color) * WEnPassant)
  694. fiftyMoveRuleReset = true
  695. if (game.board[move.dest] != 0):
  696. fiftyMoveRuleReset = true
  697. sequence.add(game.genLegalMoves(start, color))
  698. if (move in sequence):
  699. game.board.removeEnPassant(color)
  700. if (piece == WKing * ord(color) and (start - dest == (W+W))):
  701. return game.castling(start, true, color)
  702. elif (piece == WKing * ord(color) and (start - dest == (E+E))):
  703. return game.castling(start, false, color)
  704. else:
  705. game.uncheckedMove(start, dest)
  706. game.toMove = Color(ord(game.toMove)*(-1))
  707. if createEnPassant:
  708. game.board[dest-(N*ord(color))] = WEnPassant * ord(color)
  709. if capturedEnPassant:
  710. game.board[dest-(N*ord(color))] = 0
  711. if ((90 < dest and dest < 99) or (20 < dest and dest < 29)) and
  712. game.board[dest] == WPawn * ord(color):
  713. game.board[dest] = prom
  714. var prevBoard = game.previousBoard
  715. var prevCastle = game.previousCastleRights
  716. game.previousBoard.add(game.board)
  717. game.previousCastleRights.add(game.moved.genCastleRights())
  718. game.fiftyMoveCounter = game.fiftyMoveCounter + 1
  719. if fiftyMoveRuleReset:
  720. game.fiftyMoveCounter = 0
  721. return true
  722. proc hasNoMoves(game: Game, color: Color): bool =
  723. ## Returns true if the `color` player has no legal moves in a `game`.
  724. return (game.genLegalMoves(color) == @[])
  725. proc isCheckmate*(game: Game, color: Color): bool =
  726. ## Returns true if the `color` player is checkmate in a `game`.
  727. return game.hasNoMoves(color) and game.isInCheck(color)
  728. proc threeMoveRep(game: Game): bool =
  729. ## Returns true if a 3-fold repitition happened on the last move of the
  730. ## `game`.
  731. var lastState = game.previousBoard[game.previousBoard.high]
  732. var lastCastleRights = game.previousCastleRights[game.previousBoard.high]
  733. var reps = 0
  734. for stateInd in (game.previousBoard.low)..(game.previousBoard.high):
  735. if (game.previousBoard[stateInd] == lastState and game.previousCastleRights[
  736. stateInd] == lastCastleRights):
  737. reps = reps + 1
  738. return reps >= 3
  739. proc fiftyMoveRule(game: Game): bool =
  740. ## Returns true if a draw can be claimed by the 50 move rule in a `game`.
  741. return game.fiftyMoveCounter >= 100
  742. proc isDrawClaimable*(game: Game): bool =
  743. ## Returns true if a draw is claimable by either player.
  744. return game.threeMoveRep() or game.fiftyMoveRule()
  745. proc checkInsufficientMaterial(board: Board): bool =
  746. ## Checks for combinations of pieces on a `board`, where no checkmate can be
  747. ## forced.
  748. ## Returns true if no player can force a checkmate to the other.
  749. var wp = 0
  750. var wn = 0
  751. var wb = 0
  752. var wr = 0
  753. var wq = 0
  754. var bp = 0
  755. var bn = 0
  756. var bb = 0
  757. var br = 0
  758. var bq = 0
  759. for field in board.low..board.high:
  760. case board[field]:
  761. of WPawn:
  762. wp = wp + 1
  763. of BPawn:
  764. bp = bp + 1
  765. of WKnight:
  766. wn = wn + 1
  767. of BKnight:
  768. bn = bn + 1
  769. of WBishop:
  770. wb = wb + 1
  771. of BBishop:
  772. bb = bb + 1
  773. of WRook:
  774. wr = wr + 1
  775. of BRook:
  776. br = br + 1
  777. of WQueen:
  778. wq = wq + 1
  779. of BQueen:
  780. bq = bq + 1
  781. else:
  782. continue
  783. let wpieces: PieceAmount = (wp, wn, wb, wr, wq)
  784. let bpieces: PieceAmount = (bp, bn, bb, br, bq)
  785. return (wpieces in InsufficientMaterial) and (bpieces in InsufficientMaterial)
  786. proc isStalemate*(game: Game, color: Color): bool =
  787. ## Returns true if the `color` player is stalemate in a `game`.
  788. return (game.hasNoMoves(color) and not game.isInCheck(color)) or
  789. game.board.checkInsufficientMaterial()