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.

824 lines
29 KiB

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