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.

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